home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Windows Expert
/
Windows Expert.iso
/
windownt
/
wpj1_8.zip
/
WPJV1N8.TXT
< prev
Wrap
Text File
|
1993-09-04
|
183KB
|
4,301 lines
WW WW WW PPPPPPPP JJ
WW WW WW PP PP JJ
WW WWWW WW PP PP JJ
WW WW WW WW PPPPPPPP JJ
WW WW WW WW PP JJ JJ
WWWW WWWW PP JJ JJ
WW WW PP JJJJJ
----------------------------------------------------------------
The Windows Programmer's Journal Volume 01
Copyright 1993 by Peter J. Davis Number 08
and Mike Wallace Sep 93
----------------------------------------------------------------
A monthly forum for novice-advanced programmers to share ideas and
concepts about programming in the Windows (tm) environment. Each
issue is uploaded to the info systems listed below on the first of the
month, but made available at the convenience of the sysops, so allow
for a couple of days.
You can get in touch with the editors via Internet or Bitnet at:
Internet: 71644.3570@compuserve.com
CompuServe: 71655,3570 (Pete) or 71141,2071 (Mike)
GEnie: P.DAVIS5
America Online: PeteDavis
Delphi: PeteDavis
or you can send paper mail to:
Windows Programmer's Journal
9436 Mirror Pond Dr.
Fairfax, Va. 22032
We can also be reached by phone at: (703) 503-3165.
The WPJ BBS can be reached at: (703) 503-3021.
The WPJ BBS is currently 2400 Baud (8N1). We'll be going to 14,400 in
the near future, we hope.
LEGAL STUFF
- Microsoft, MS-DOS, Microsoft Windows, Windows NT, Windows for
Workgroups, Windows for Pen Computing, Win32, and Win32S are
registered trademarks of Microsoft Corporation.
- Turbo Pascal for Windows, Turbo C++ for Windows, and Borland C++ for
Windows are registered trademarks of Borland International.
- WordPerfect is a registered trademark of WordPerfect Corporation.
- Other trademarks mentioned herein are the property of their
respective owners.
- WPJ is available from the WINSDK, WINADV and MSWIN32 forums on
CompuServe, and the IBMPC, WINDOWS and BORLAND forums on Genie. It is
also available on America Online in the Programming library. On
Internet, it's available on WSMR-SIMTEL20.ARMY.MIL and
FTP.CICA.INDIANA.EDU. We upload it by the 1st of each month and it is
usually available by the 3rd or 4th, depending on when the sysops
receive it.
- The Windows Programmer's Journal takes no responsibility for the
content of the text within this document. All text is the property and
responsibility of the individual authors. The Windows Programmer's
Journal is solely a vehicle for allowing articles to be collected and
distributed in a common and easy to share form.
- No part of the Windows Programmer's Journal may be re-published or
duplicated in part or whole, except in the complete and unmodified
form of the Windows Programmer's Journal, without the express written
permission of each individual author. The Windows Programmer's Journal
may not be sold for profit without the express written permission of
the Publishers, Peter Davis and Michael Wallace, and only then after
they have obtained permission from the individual authors.
Table of Contents
Official Motto: Served only in the finest restaurants.
Bootup
WPJ.INI . . . . . . . . . . . . . . . . . . 4 Pete Davis
Letters . . . . . . . . . . . . . . . . . . 6 Readers
WPJ Survey . . . . . . . . . . . . . . . . . 11
Programming
Beginner's Column . . . . . . . . . . . . . 14 Dave Campbell
Hacker's Gash . . . . . . . . . . . . . . . 25 Dennis Chuah
GDI See GDI Do . . . . . . . . . . . . . . . 34 Bernard Andrys
Windows Hooks . . . . . . . . . . . . . . . 46 David S. Browne
Shared Global Memory . . . . . . . . . . . . 51 Dennis Chuah
Customizing File Dialogs in Visual C+ . . . 59 Tony Lee
Software
Installing Windows NT . . . . . . . . . . . 63 Kurt Simmons
Special Report
Software Development '93 . . . . . . . . . . 65 Pete Davis
The Leftovers
Getting In Touch with Us . . . . . . . . . . 67 Pete & Mike
Last Page . . . . . . . . . . . . . . . . . 68 Mike Wallace
Windows Programmer's Journal Staff:
Publishers . . . . . . . . . . . . . . . . . Pete and Mike
Editor-in-Chief . . . . . . . . . . . . . . . Pete Davis
Managing Editor . . . . . . . . . . . . . . . Mike Wallace
Contributing Editor . . . . . . . . . . . . . David Campbell
Contributing Editor . . . . . . . . . . . . . Dennis Chuah
Graphic Artist (Bad) . . . . . . . . . . . . Pete Davis
Graphic Artist . . . . . . . . . . . . . . . Mark Coghlan
Graphic Artist . . . . . . . . . . . . . . . Dennis Chuah
Graphic Artist . . . . . . . . . . . . . . . Bernard Andrys
Contributing Writer . . . . . . . . . . . . . Dennis Chuah
Contributing Writer . . . . . . . . . . . . . David S. Browne
Contributing Writer . . . . . . . . . . . . . Bernard Andrys
Contributing Writer . . . . . . . . . . . . . Tony Lee
Contributing Writer . . . . . . . . . . . . . Kurt Simmons
WPJ.INI
by Pete Davis
Well, the survey results continue to pour in. Keep them coming.
If you haven't filled one out, there's another copy in this issue. The
last day for the prize is September 30th. That means post-marked or
the e-mail is dated by September 30th. We will do the drawing shortly
after.
Speaking of the survey, I thought I'd give you all an idea of
what the results have been like. A lot of you like the fact that we
cover a wide variety of levels. We are commited to providing
information for novice programmers as well as more advanced
programmers.
We got a lot of good and bad feedback. In a sense, all feedback
is good. When I say bad, I really mean critical. And that's a good
thing. One thing that disturbed me is this. We got a few responses
that mentioned errors, and one saying "Gross Errors", in the magazine.
YES, there are "Gross Errors". To date, Mike and I roughly skim the
articles. We check spelling and grammer. We don't do a whole lot more.
Why? We don't have time. It takes at least 25 hours a month to put the
magazine together as it is. I went through this last month, and some
people don't seem to get it. That's not the only problem. As I said
last month, we don't pay the writers and have a hard enough time
getting them. If we made it difficult for them by making them do
re-writes, we'd be putting out the Windows Programmer's Nothing. I'm
serious about that. There are a couple people who might put up with it
for a while, but if I wasn't getting paid, I sure wouldn't make a
habit of it, and I don't blame our writers for having the same feeling
about that. If you're going to put up with serious editing, you may as
well get paid for it.
As far as "Gross Errors", Mike and I have always been willing to
publish corrections, which we hoped we'd get more of, but people
haven't sent them in. I got one in the reader's survey, and here it
is:
In Bernard Andrys' article "GDI See GDI Do", he said that the
PASCAL keyword in a function declaration meant that the parameters
were passed by reference and not by value. (That was the gist that I
got, anyway.) This is incorrect. What it actually has to do with is
the way parameters are placed on the stack. The C calling convention
places the arguments on the stack from right to left. For example, the
call, myFunction(a, b, c) pushes the contents onto the stack starting
with 'c' and ending with 'a'. The Pascal calling convention places the
arguments on the stack from left to right, so with the previous
example, 'a' is pushed onto the stack first, followed by 'b' and then
'c'. This gives C the ability to handle an unspecified number of
parameters on the stack. It makes library calls like printf()
possible. Although Pascal allows an unspecified number of parameters
to be used for the Read, Write and some other procedures, this is
handled by the compiler at compile time. In C, the parameters are
handled at run-time by the function.
OK, that was easy and relatively painless. If anyone else has
corrections, send them in. We'd be more than happy to tell everyone
else. Our original intent was for this magazine to be a collective
- 4 -
effort. With many of our readers and writers, it has been able to be
that. It bothers me, though, when people say, "you should fix this, do
that, fix that, and stop doing this..." and go on through a list 20
things and then say that they still want it for free. Mike and I have
done the best job we could given the restrictions on our time. This
goes for the writers we've had and still have. They should all be
applauded for their efforts in getting articles done and putting in
the time.
I'm probably making a big deal out of a little thing, in fact, I
am. Most of you had few or no complaints. I have complaints about the
magazine, it's not perfect by any stretch, but most of you were very
complimentary. We thank you all. Mike and I have enjoyed doing the
magazine and we'll hopefully enjoy continuing it for years to come.
The survey, all-in-all was a great success. It has given us a
better idea of the kinds of things we should focus on and the kinds of
things we might want to consider leaving behind. Does that mean you
should stop sending your comments, suggestions and critiques. No sir
(ma'am)! Send them in. For those of you who have complaints, why did
you wait until the survey to send them in? Send them in as soon as you
have them. If there are problems with the magazine, we want to know
about it. We want to do our best to please as many of you as possible.
Ok, so where does everything stand? At some point Mike and I
would like to actually start printing the magazine. Many of you said
you'd like us to keep the WinHelp format even if we go to print. We
will do that. We don't know when we'll take the magazine to print and
it may be some time. Mike and I are going to start trying to spend
more time on the magazine if possible. We're going to try to improve
the editing of the articles also. Again, all of this takes time. If
the magazine were to go to print, it would become a full time job for
Mike and I. The kind of "gross" mistakes seen in past issues would end
(hopefully!).
We will keep everyone informed of any developments with the
magazine. At this point we've talked with people involved in
publishing about the possibility of printing the magazine. How soon
that happens remains to be seen.
Peace.
Pete
- 5 -
Letters
From: Pat White <P.WHITE@fs2.mbs.ac.uk>
Date: 12 Aug 93 10:20:18 BST
Subject: WPJ Mag
Pete and Mike,
Many thanks for your efforts in producing the magazine - I'm
brand new to Windows programming and am finding a lot of useful info.
Just one very minor criticism - when you review shareware could you
always include where it can be found, especially if it is in either
the Simtel or CICA archive as both are widely mirrored here in Europe.
I am including in this note two lists you might want to include
in a future issue. The file index is a combination of the READ.ME
files, The article index is built from the contents of the
WPJVxNx.TXT files. They cover the first 6 issues - I hope to retrieve
number 7 today.
[Editor's Note: We felt this information would be useful for everyone,
so it's included here and also seperately as an ASCII file. Thanks
Pat.]
File index
Windows Programmer's Journal
Volume 1 Number 1
January 1993
The following files are contained in this archive:
TRASH.PAS Pascal Source for trash can
TRASH.RC Resource code for trash can
SUBMIT.TXT How to submit an article to WPJ
LINKLIST.C C source code for linked list
LINKLIST.MAK Microsoft nMake compat make file
LINKLIST.DEF Module Definition file for linked list
LINKLIST.RC Resource file for Linked List
LINKLIST.H Header file for Linked List
LINKLIST.ICO Icon for Linked List
MAKELIST.BAT MSC 6.00 with 3.0 SDK batchfile to make
PMDDE.C Program Manager DDE C source code
WPJV1N1.TXT Windows Programmer's Journal Volume 1 Number 1
README.TXT Take a guess.
Volume 1 Number 2
February 1993
The following files are contained in this archive:
HELLO.ZIP Source Code to the Hello World Program
LINKLIST.ZIP Mike's revised Linked List program
HAXTON.ZIP Rod's DLL examples
D&DCLIE.PAS Andreas Furrer's Drag and Drop Client Program
D&DSERV.PAS Andreas Furrer's Drag and Drop Server Program
WPJV1N2.TXT Windows Programmer's Journal Volume 1 Number 2
- 6 -
READ.ME Well, I screwed it up in the last issue (pete).
SUBMIT.TXT How to submit articles to us.
Volume 1 Number 3
March 1993
The following files are contained in this archive:
WPJV1N3.TXT The magazine in plain text format
WPJV1N3.HLP The magazine in Windows Help format
WPJ.BAT Quick way to view the WPJV1N3.HLP file in Windows
BEGINNER.ZIP Beginner's Column source code
OWNERBTN.ZIP Owner Draw Buttons source code.
INSTALL3.ZIP Source code for the Install Program article
PAINT.ZIP Advanced C++ source code
READ.ME Guess!
SUBMIT.TXT How to submit articles to us.
Volume 1 Number 4
April 1993
The following files are contained in this archive:
WPJV1N3.TXT The magazine in plain text format
HELPREAD.ME Explanation of why there's no help file this month.
ROD.ZIP Rod Haxton's DLL code.
HELLO.ZIP Beginner's Column source code (Dave Campbell)
ODLBOX.ZIP Owner-Draw list boxes
READ.ME Guess!
SUBMIT.TXT How to submit articles to us.
Volume 1 Number 5
May 1993
The following files are contained in this archive:
WPJV1N5.TXT The magazine in plain text format
WPJV1N5.HLP The magazine in WinHelp format
HELLO.ZIP Beginner's Column source code (Dave Campbell)
WINSTUB.ZIP Windows STUB source code
READ.ME Guess!
SUBMIT.TXT How to submit articles to us.
Volume 1 Number 6
June 1993
The following files are contained in this archive:
WPJV1N6.TXT The magazine in plain text format
WPJV1N6.HLP The magazine in Winhelp format
HELLO.ZIP Beginner's Column source code (Dave Campbell)
EDITPRO.ZIP Goes with Eric Glass' article in Vol. 1, No. 5.
READ.ME Guess!
SUBMIT.TXT How to submit articles to us.
- 7 -
Article index
Volume 1 Number 1
-----------------------------------------------------------------
WPJ.INI ...................................... 3 Pete Davis
Off Topic .................................... 6 Pete & Mike
Beginner's Corner (C) .................... 8 Pete Davis
& Mike Wallace
A Drag and Drop Trashcan (TPW) ............... 16 Andreas Furrer
Using DDE to Communicate With Program Manager. 18 Pete Davis
Implementing a Linked List in the Global Heap. 22 Mike Wallace
Book Review .................................. 26 Pete Davis
Last Page .................................... 28 Mike Wallace
Getting in Touch with Us ..................... 29 Pete & Mike
Volume 1 Number 2
-----------------------------------------------------------------
WPJ.INI ....................................... 3 Pete Davis
Letters ....................................... 5 Readers
Install Program Part II ....................... 7 Pete Davis
Programming a Drag&Drop Server ................ 9 Andreas Furrer
C++ Beginner's Column ......................... 12 Mike Wallace
Beginner's Corner (C) ................... 14 Pete Davis
Using LZExpand Library ........................ 18 Alex Fedorov
Implementing a Linked List - Revisited ........ 21 Mike Wallace
An Introductory Look at DLLs and Make Files ... 22 Rod Haxton
The Windows Help Magician ..................... 29 Jim Youngman
Last Page .................................... 30 Mike Wallace
Getting in Touch with Us ..................... 31 Pete & Mike
Volume 1 Number 3
-----------------------------------------------------------------
WPJ.INI ....................................... 4 Pete Davis
Letters ....................................... 7 Readers
Beginner's Column ............................. 10 Dave Campbell
Install Program Part III ...................... 20 Pete Davis
Home Cooking - C++ From Scratch ............... 22 Andrew Bradnan
Creating and Using Owner Draw Buttons ......... 26 Todd Snoddy
Hacker's Gash ................................. 30 Mike and Pete
Special News .................................. 32 Mike Wallace
Windows 3.1: Using Version Stamping Library ... 33 Alex Fedorov
Book Review ................................... 36 Pete Davis
Book Review ................................... 38 Mike Wallace
Printing in Windows ........................... 40 Pete Davis
Advanced C++ and Windows ...................... 45 Andrew Bradnan
Trials and Tribulations Part 1 ................ 54 Jim Youngman
Getting in Touch with Us ..................... 57 Pete & Mike
Last Page .................................... 58 Mike Wallace
Volume 1 Number 4
-----------------------------------------------------------------
WPJ.INI ....................................... 4 Pete Davis
Letters ....................................... 6 Readers
Midlife Crisis: Windows at 32 ................. 9 Pete Davis
- 8 -
Beginner's Column ............................. 14 Dave Campbell
Owner-Drawn List Boxes ........................ 25 Mike Wallace
Beginner's Column for Turbo Pascal for Windows 30 Bill Lenson
Hacker's Gash ................................. 31 Readers
Microsoft's Windows Strategy .................. 34 Pete Davis
Accessing Global Variables Across DLL ......... 38 Rod Haxton
Getting A Piece Of The Future ................. 41 Peter Kropf
The "ClickBar" Application .................... 44 WynApse
Getting in Touch with Us ..................... 45 Pete & Mike
Last Page .................................... 46 Mike Wallace
Volume 1 Number 5
-----------------------------------------------------------------
WPJ.INI ....................................... 4 Pete Davis
Beginner's Column ............................ 6 Dave Campbell
Creating Windows Text File Editors ........... 15 Eric Grass
Midlife Crisis: Windows at 32 ................ 20 Pete Davis
WDASM - Review of a Windows Disassembler ..... 23 Pete Davis
Hypertext, Sex and Winhelp ................... 25 Loewy Ron
Enhancing the WINSTUB Module ................. 30 Rodney Brown
Microsoft Developers Network .................. 33 Dave Campbell
Getting in Touch with Us ..................... 35 Pete & Mike
Last Page .................................... 36 Mike Wallace
Volume 1 Number 6
--------------------------------------------------- ------
WPJ.INI ..................................... 4 Pete Davis
Letters ..................................... 8 Readers
Beginner's Column ........................... 12 Dave Campbell
Internally Yours ............................ 15 Pete Davis
Pascal in 21 Steps .......................... 18 Bill Lenson
Moving Away from ... ....................... 29 Pete Davis
C++ Beginner's Column ....................... 30 Rodney Brown
WPJ BBS Update .............................. 31 Pete Davis
Windows Messaging ........................... 32 Mike Wallace
WinEdit Review .............................. 35 Jeff Perkell
White House Letter .......................... 38 Mike Strock
Text Manager ................................ 40 Mike Wallace
Getting in Touch with Us .................... 42 Pete & Mike
Last Page ................................... 43 Mike Wallace
Keep up the good work.
Pat White
Pat White, Manchester Business School, Manchester, UK
JANET address - P.WHITE@UK.AC.MBS.FS2
EARN/BITNET - P.WHITE@FS2.MBS.AC.UK
INTERNET - P.WHITE@FS2.MBS.AC.UK
or - P.WHITE%FS2.MBS.AC.UK@NSFNET-RELAY
UUCP/USENET - P.WHITE%FS2.MBS.AC.UK@NSFNET-RELAY
[Thanks for the note and the index, Pat. I think people will find
this useful. We'll have to start doing this every six issues or so.
- 9 -
- mfw]
- 10 -
WPJ Survey
Down the page is a survey. Please fill it out as completely as
possible. Don't forget to put your address so we can mail your prize
if you win.
The prize options are:
1) Win32 Reference Manuals (5-book set)
2) "Windows API Bible", "Windows Internals" and "Undocumented Windows"
3) Windows NT Resource Kit
Either print it out, fill it in, and send it via regular mail or
edit it with a text editor and fill in the blanks and send it by
E-Mail.
On Compuserve : 71644,3570
On Internet : 71644.3570 at compuserve.com
On GEnie : P.DAVIS5
On America Online: PeteDavis
On Delphi : PeteDavis
or regular mail to:
WPJ Reader Survey
9436 Mirror Pond Dr.
Fairfax, Va 22032 U.S.A.
Thanks for taking the time to fill out the survey.
Pete & Mike
- 11 -
Windows Programmer's Journal
Reader Survey: August, 1993
Name:_______________________________ E-Mail Addresses
Profession:_________________________ Service Address
Title:______________________________ ___________ _______________
Company:____________________________ ___________ _______________
Address:____________________________ ___________ _______________
____________________________
____________________________
Phone #:_________________ Fax #:________________
Primary operating system on your computer:
________________________________
S e c o n d a r yo p e r a t i n g s y s t e m :
_______________________________________________
Primary programming language used (include brand):
________________________
O t h e r P r o g r a m m i n g l a n g u a g e s u s e d :
_________________________________________
L i b r a r i e s o r C l a s s L i b r a r i e s u s e d :
________________________________________
Development tools used (debuggers, editors, etc. Include brand.):
_________________________________________________________________
_________________________________________________________________
What I Like about the Windows Programmer's
Journal:______________________________________________________________
___________________________________
What I don't like about Windows Programmer's
Journal:______________________________________________________________
_________________________________
Things I'd like to see in the Windows Programmer's
Journal:______________________________________________________________
___________________________________
O t h e r p r o g r a m m i n g m a g a z i n e s I
read:_______________________________________
Would you pay for printed copies of WPJ? (Circle one) YES NO
- 12 -
Prize I want:_____________________________________
- 13 -
Beginner's Column
By Dave Campbell
Remember when you were in school, and the teachers would show you
the REAL hard way to do something, and just about the time you thought
you understood it enough, they would show you the easy way? Well, I'm
going to do that to you right now. The File Open box we've worked on
the last two issues is going to go away, and be replaced by a single
call to the Common Dialogs DLL, COMMDLG.DLL.
Common Dialogs allow the developer to call up a functional dialog
box with a single call, rather than build the dialog box, compile in
the dialog resource, write the code for handling the dialog box
messages, etc. At the same time, the developer gets dialog boxes for
quite a few of the standard configuration items that look identical to
the ones everyone else is using, thereby making life easier for the
user.
COMMDLG.DLL
COMMDLG.DLL contains dialogs for the following:
ChooseColor
ChooseFont
FindText
GetFileTitle
GetOpenFileName
GetSaveFileName
PrintDlg
ReplaceText
These may be used in Windows 3.0 or 3.1, but only in enhanced mode.
My intention was to show the use of just the GetOpenFileName box,
but it seemed so easy, I went ahead and added an implementation of the
ChooseColor box and ChooseFont box because they always seems to dress
up an application. Once you see how easy this is, filling out the
remainder of the menu selections with standard dialog boxes will
become quite a bit less of a "chore".
Removed Code
First I'll quickly run through what we can throw away from last
issue:
1) All the Fileo.* files, including the DLL that we built last
time
2) The DoFileOpenDlg procedure in Hello.c
Hello.C
The #include of "fileo.h" is no longer needed, because we aren't
using that DLL. However, we do need to add
- 14 -
#include <commdlg.h>
to pick up the items defined there.
Since we aren't going to use Fileo.DLL, we do not need
HANDLE hFileo
but we will need
HANDLE hCommDlg
so we just change that line, and the lines where the DLL is loaded to
load COMMDLG.DLL and get its handle into hCommDlg.
'pof' and 'of' are removed, as they were used with the old code.
szFileSpec and szDefExt are moved to WndProc, and we add:
COLORREF ColorRef;
HANDLE MyFont = NULL;
COLORREF
COLORREF is nothing more than a DWORD. Defining it as such,
however, gives us a way to remember we are using an RGB color value.
The RGB method of defining colors is a standardized way that will
carry us into the 24-bit arena of colors, but is still useful on
systems with 16 colors. Programming in this manner means a little
overhead up-front, but ease of programming, and portability later on.
The ColorRef variable defined here will be used to set the color of
our Hello World text via a common dialog call.
MyFont
MyFont is a handle to the font we are going to select in the
ChooseFont dialog box. Initially it is set to NULL, and this fact will
be used in the code.
WinMain
The only changes to WinMain are the loading and freeing of
COMMDLG.DLL instead of Fileo.DLL, and the freeing of the handle,
if (MyFont != NULL)
DeleteObject(MyFont);
WndProc
WndProc is the place where all the action happens, so most of the
changes are here.
- 15 -
Variables
The following are defined for our new version:
OPENFILENAME openfile;
char szFilter[25];
char szExt[4 + 1];
char szDirName[128];
char szFileSpec[16];
char szDefExt[5];
char szFileTitle[128];
int i;
COLORREF LocColorRef[16];
CHOOSECOLOR ChooseMyColor;
DWORD dwFlags=CF_SCREENFONTS;
CHOOSEFONT ChooseMyFont;
LOGFONT LogMyFont;
HANDLE TempFont;
OPENFILENAME is a structure used by the file dialogs in the
common dialog DLL. It is defined as:
typedef struct tagOPENFILENAME
{
DWORD lStructSize;
HWND hwndOwner;
HINSTANCE hInstance;
LPCSTR lpstrFilter;
LPSTR lpstrCustomFilter;
DWORD nMaxCustFilter;
DWORD nFilterIndex;
LPSTR lpstrFile;
DWORD nMaxFile;
LPSTR lpstrFileTitle;
DWORD nMaxFileTitle;
LPCSTR lpstrInitialDir;
LPCSTR lpstrTitle;
DWORD Flags;
UINT nFileOffset;
UINT nFileExtension;
LPCSTR lpstrDefExt;
LPARAM lCustData;
UINT (CALLBACK *lpfnHook) (HWND, UINT, WPARAM, LPARAM);
LPCSTR lpTemplateName;
} OPENFILENAME;
We are only going to use the minimum in our example, but I will
mention the unused ones, to pique your interest to experiment.
CHOOSECOLOR
CHOOSECOLOR is a structure used by the color dialogs in
commdlg.DLL:
- 16 -
typedef struct tagCHOOSECOLOR
{
DWORD lStructSize;
HWND hwndOwner;
HWND hInstance;
COLORREF rgbResult;
COLORREF FAR* lpCustColors;
DWORD Flags;
LPARAM lCustData;
UINT (CALLBACK* lpfnHook)(HWND, UINT, WPARAM, LPARAM);
LPCSTR lpTemplateName;
} CHOOSECOLOR;
Again, before we are finished, all the entries will be explained.
CHOOSEFONT
CHOOSEFONT is a structure used by the dialogs in commdlg.DLL:
typedef struct tagCHOOSEFONT
DWORD lStructSize;
HWND hwndOwner;
HDC hDC;
LOGFONT FAR* lpLogFont;
int iPointSize;
DWORD Flags;
COLORREF rgbColors;
LPARAM lCustData;
UINT (CALLBACK* lpfnHook)(HWND, UINT, WPARAM, LPARAM);
LPCSTR lpTemplateName;
HINSTANCE hInstance;
LPSTR lpszStyle;
UINT nFontType;
int nSizeMin;
int nSizeMax;
} CHOOSEFONT;
LOGFONT
LOGFONT is a structure used by the dialogs in commdlg.DLL
defining the characteristics of a font for drawing text on a window:
typedef struct tagLOGFONT
int lfHeight;
int lfWidth;
int lfEscapement;
int lfOrientation;
int lfWeight;
BYTE lfItalic;
BYTE lfUnderline;
BYTE lfStrikeOut;
BYTE lfCharSet;
BYTE lfOutPrecision;
BYTE lfClipPrecision;
BYTE lfQuality;
BYTE lfPitchAndFamily;
- 17 -
BYTE lfFaceName[LF_FACESIZE];
} LOGFONT;
The first change to WndProc is in the WM_CREATE message handler.
Since we are gong to be changing colors, we assign ColorRef to be
black:
ColorRef = RGB(256, 256, 256);
and continue on. Then in the WM_PAINT handler, we use ColorRef in the
SetTextColor call:
SetTextColor(hdc, ColorRef);
Now when we change ColorRef, and force a WM_PAINT message, the
text will be painted in a different color of our choice. Another
change made to the WM_PAINT message handler is to check for a font
change:
if (MyFont != NULL)
TempFont = SelectObject(hdc, MyFont);
DrawText(...);
if (MyFont != NULL)
SelectObject(hdc, TempFont);
If MyFont is set to a handle value, that font is selected into
use by doing a SelectObject call. The return value is the previous
handle, which we save in 'TempFont'. After displaying the text, we put
the old font back into use by doing another SelectObject call. This
will keep other display areas in our hdc from using the same font. In
this particular application, it may not be necessary, but good habits
are hard to break.
Files
The IDM_FILEOPEN message handler has changed quite a bit. We've
removed the call to our DLL, and added the minimum that it would take
to talk to the common dialog box GetOpenFileName procedure.
We first use the 'memset' command to set the structure openfile
to all nulls, and insert the structure size into the structure at
lStructSize:
case IDM_FILEOPEN :
memset(&openfile, 0, sizeof(OPENFILENAME));
openfile.lStructSize = sizeof(OPENFILENAME);
We then declare the handle of the owner of this call as hWnd.
This handle is used in the HELP call from the dialog box, to route the
help message back to us:
openfile.hwndOwner = hWnd;
In the standard file dialog boxes, there is always a combo box
labeled "List Files of Type", and the list box contains entries such
- 18 -
as "Text files (*.txt)" or "All files (*.*)". The developer defines
those entries via the szFilter and szCustomFilter strings.
Each of the filter strings is a concatenation of string pairs
each null-terminated, and the two filters are terminated by a double
null, one for the final filter declaration, and one for the filter
itself. This is reasonably confusing, but maybe a declaration will
clear it up. If we want to have a filter of "Text files" be tied to
"*.txt", we could fill our filter string as follows:
sprintf(filter, "%s%c%s%c%c", "Text files", '\0', "*.txt",
'\0', '\0');
An alternate method is useable at run-time, or if you define
strings in a string table. Substitute an unused character for your
string separator, and then at run-time, change all those to '\0':
strcpy(szFilter, "Text Files|*.txt|");
for (i = 0; szFilter[i] != '\0'; i++)
{
if (szFilter[i] == '|')
szFilter[i] = '\0';
}
openfile.lpstrFilter = (LPSTR)szFilter;
openfile.nFilterIndex = 1;
Note the FilterIndex begins with "1", not 0. A value of 0
instructs Windows to use the CustomFilter string instead.
lpstrFile is a pointer to our returned file name, which we
initialize to null:
szFileName[0] = '\0';
openfile.lpstrFile= (LPSTR)szFileName;
openfile.nMaxFile = sizeof(szFileName);
If we insert a file spec in here, that is the name to which the
box is initialized. This string must be at least 256 characters to
avoid problems with Windows.
The FLAGS entry declares various parameters controlling the
operation of the dialog box. The parameters we are using are:
OFN_SHOWHELP | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST
OFN_SHOWHELP instructs the commdlg code to display a help button
in the dialog. As explained above, this will assert the help for our
window, therefore the hwndOwner handle must not be NULL.
OFN_PATHMUSTEXIST instructs the commdlg code to only allow the
user to type a path specification that exists currently on the system.
An error is displayed if the path is non-existent.
OFN_FILEMUSTEXIST is similar to OFN_PATHMUSTEXIST in that only
files that currently exist on the currently enabled path are allowed
to be typed. Selecting OFN_FILEMUSTEXIST automatically selects
OFN_PATHMUSTEXIST, but it is a helpful reminder to list both.
- 19 -
There are other parameters that may be defined for selecting
multiple files, kicking off a new file message, enabling a hook
function (explained below) or using a custom dialog box template,
among others.
Next comes the call that does all the work:
GetOpenFileName(&openfile);
Normally, the result of this would be checked for errors, but in
this application, we aren't searching for a file, or intending to open
one. We are simply demonstrating the use of the dialog.
Staying standard with previous articles, we copy the returned
file name to OutMsg, and finish this message handler:
lstrcpy(OutMsg, openfile.lpstrFile);
break;
Other parameters
The unused parameters of the OPENFILENAME structure are:
lpTemplateName declares a custom dialog template to be used in place
of the common one. To do this, hInstance must be set to the instance
value of the window owning the template, and the FLAGS value must
enable templates.
nMaxCustFilter contains the size of the custom filter, if one is used.
lpstrFileTitle is a string that will receive the file name and
extension only, that is the path specification will be stripped.
nMaxFileTitle is the length of lpstrFileTitle.
lpstrInitialDir is the starting directory of the dialog box. If this
is NULL, the current directory is used. If, however, lpstrFile
contains the full path specification and file specification for a
file, the path portion of lpstrFile is used as the initial directory.
lpstrTitle is a user-definable title for the dialog box. A NULL will
cause a standard default value to be used.
nFileOffset is a returned value denoting where in the lpstrFile string
the filename begins.
nFileExtension is a returned value denoting where in the lpstrFile
string the file extension begins.
lpstrDefExt is a user-definable default extension to be used in the
search if the user does not type an extension on a filename.
lpfnHook is a pointer to a function that snags dialog box messages
prior to the common dialog box handler, allowing the user to
intercept, or pre-process calls.
lCustData defines the data that is passed to the hook function.
- 20 -
That should be enough variation to make anyone pleased with
"common" dialogs. And that is only one of the set.
Color
To enable a developer to allow an end-user to define colors for
text or dialogs, the ChooseColor common dialog was built. An extra
entry in the menu structure of our code, under "SAMPLE" was declared
as IDM_COLOR, and handled as follows:
case IDM_COLOR :
memset(&ChooseMyColor, 0, sizeof(CHOOSECOLOR));
ChooseMyColor.lStructSize=sizeof(CHOOSECOLOR);
As with the File dialog, the CHOOSECOLOR structure must be
declared, and its size passed to the dialog.
The initial 16 custom colors are declared going into the dialog,
and coming out of the dialog via lpCustColors:
ChooseMyColor.lpCustColors=LocColorRef;
The handle of the window owning the dialog is declared as with
the file dialog:
ChooseMyColor.hwndOwner = hWnd;
rgbResult is the value that is initially displayed with the
dialog is opened, and the value that is returned by the user:
ChooseMyColor.rgbResult = RGB(256, 256, 256);
As with the file dialogs, flags may enable hooks, templates, help
buttons, and control the full usage of the dialog. In our example, we
have chosen to use the rbgResult value as the initially selected
value.
ChooseMyColor.Flags = CC_RGBINIT;
The action happens with "ChooseColor". The dialog box is
displayed, and the user is allowed to choose colors as in the control
panel of Windows. The result may be checked for an error value (other
than 0), and the resulting color is returned via rgbResult.
if (ChooseColor(&ChooseMyColor))
{
ColorRef = ChooseMyColor.rgbResult;
InvalidateRect(hWnd, NULL, FALSE);
UpdateWindow(hWnd);
}
break;
In our case, rbgResult is written to ColorRef, and used when a
repaint takes place, allowing the user to pick the color with which
Hello/Goodbye Windows is painted. A repaint is forced by Invalidating
the entire window client area (the NULL parameter), and not painting
the background (the FALSE parameter).
- 21 -
UpdateWindow sends a WM_PAINT message straight to the window,
causing the InvalidateRect area to be repainted. This takes care of
the change we have made with the ChooseColor call, without having to
resize the window to see the change.
The structure CHOOSECOLOR contains lpfnHook, lCustData and
lpTemplateName as does the FILEOPEN structure.
Fonts
I don't really want to get into a full discussion of fonts at
this time, because it is extensive, but I do want to show how to use
the ChooseFont dialog box. If some of the information is sketchy,
accept it for now, and we'll dig into it later, or look it up in
Petzold.
An extra entry in the menu structure of our code, under "SAMPLE"
was declared as IDM_FONT, and handled as follows:
case IDM_COLOR :
memset(&ChooseMyFont, 0, sizeof(CHOOSEFONT));
ChooseMyFont.lStructSize = sizeof(CHOOSEFONT);
ChooseMyFont.hwndOwner = hWnd;
As with the other dialogs, the CHOOSEFONT structure must be
declared, its size passed to the dialog, and our window handle
inserted into the structure.
A variable of LOGFONT type is pointed to by the lpLogFont
structure entry, and filled out as follows:
ChooseMyFont.lpLogFont = &LogMyFont;
lstrcpy(LogMyFont.lfFaceName, "Times New Roman");
LogMyFont.lfHeight = 10;
LogMyFont.lfItalic = FALSE;
LogMyFont.lfWeight = FW_BOLD;
LogMyFont.lfStrikeOut = FALSE;
LogMyFont.lfUnderline = FALSE;
This declares 'Times New Roman' in bold 10 point type.
As I said above, at this point in time, I am choosing to ignore
the other structure variables in LOGFONT, and at least for our
application, the default values of NULL (as inserted with the
'memset') work fine.
Our ColorRef value is inserted into the ChooseMyFont structure
because color may come into play in the ChooseFont dialog:
ChooseMyFont.rgbColors = ColorRef;
The flags selected in our example are
ChooseMyFont.Flags = CF_SCREENFONTS | CF_EFFECTS
| CF_INITTOLOGFONTSTRUCT;
CF_SCREENFONTS forces the dialog box to display only the fonts
- 22 -
available currently for the screen.
CF_EFFECTS enables the strikeout, underline, and color effects of the
dialog box.
CF_INITTOLOGFONTSTRUCT inits the dialog box to the LOGFONT structure
as declared above.
Now the call to ChooseFont is made, and if successful, the font
handle 'MyFont' is set to the new font chosen in the dialog, and
ColorRef is set likewise.
if (ChooseFont(&ChooseMyFont))
{
if (MyFont != NULL)
DeleteObject(MyFont);
MyFont = CreateFontIndirect(&LogMyFont);
ColorRef = ChooseMyFont.rgbColors;
InvalidateRect(hWnd, NULL, TRUE);
UpdateWindow(hWnd);
}
break;
Include file
IDM_COLOR and IDM_FONT must be added to hello.h to allow access
to the new menu items:
#define IDM_COLOR 326
#define IDM_FONT 327
Resource file
The addition of the Color selection to the menu is defined in
hello.rc:
menuitem separator
menuitem "&Color", IDM_COLOR
menuitem "&Font", IDM_FONT
EndDialog
I have included the Microsoft make file, MHello.mak for this
issue. I received no help so far on the request from help wizards in
the last issue, so the offer still stands.
For the next issue, I had intentions of starting to discuss Help
files, but WPJ seems to have started on that already, so I will
continue with my schedule, and discuss displaying a dialog box as a
main window.
Feel free to contact me in any of the ways below. Thanks to
those of you that have e-mailed me questions; keep them coming.
- 23 -
Dave Campbell
WynApse PO Box 86247 Phoenix, AZ 85080-6247 (602)863-0411
wynapse@indirect.com
CIS: 72251,445
Phoenix ACM BBS (602) 970-0474 - WynApse SoftWare forum
---------------------------------------------------------------
ClickBar consists of a Dynamic Dialog pair that allows C developers
for Windows 3.0/3.1 to insert a "toolbar" into their application.
Microsoft, Borland, etc. developers may display a toolbar or tool
palette inside their application, according to a DLG script in their
.RC or .DLG file.
Borland developers may install ClickBar as a custom control, providing
three custom widgets added to the Resource Workshop palette. These
are three different button profiles defining 69 custom button images
total. The buttons may be placed and assigned attributes identically
to the intrinsic Resource Workshop widgets.
Source is provided for a complete example of using the tools,
including the the use of custom (owner-drawn) bitmaps for buttons.
Version 1.5 uses single-image bitmaps to reduce the DLL size, and
includes source for a subclass example for inserting a toolbar.
Registration is $35 and includes a registration number and printed
manual.
WynApse
PO Box 86247 (602) 863-0411
Phoenix, AZ 85050-6247 CIS: 72251,445
- 24 -
Hacker's Gash
By Dennis Chuah
In this instalment of Hacker s Gash, we will look at system level
entities, the ToolHelp library and extend the discussion to private
class dialogboxes. Plus, we will continue last month s metafile
discussion. Here is a summary of the topics presented:
1. Instances, Modules and Tasks
2. When Should HINSTANCES, HMODULES and HTASKS be Used
3. List of Modules
4. Private Dialogbox Class
5. Metafiles revisited
All example programs are compiled with Borland C++ with the
following options:
Memory model: MEDIUM
Optimization: Fastest Code
Entry/Exit Code: Smart Callbacks
Floating Point: None
Instruction Set: 80286
Calling Convention: C
Instances, Modules and Tasks.
Most of you should have come across instances. Windows passes a
handle to an application (HINSTANCE) in the WinMain function. Calls
made to RegisterWindow and CreateWindow require this value. A
HINSTANCE is something you have used often but have you stop to think
of what a HINSTANCE is? Then there is the handle to a module
(HMODULE) and the handle to a task (HTASK). You have heard of them
before, but have never needed to use them. What are they anyway?
I wrote a .DLL and an application to illustrate these system
entities. The source code and executable are in the file GASH.ZIP, in
the TEST subdirectory. This .ZIP file contains subdirectories, so
remember to use the -d flag when unzipping it. Figure 1a shows the
output of the application.
Figure 1a
I then ran another instance of the application and the result is shown
in figure 1b.
Figure 1b
With the aid of figures 1a and 1b, we can see that the HINSTANCE
is unique for different instances of the application. This is
sensible -- HINSTANCE uniquely identifies an application instance.
The HINSTANCE for the .DLL however, is the same across different
instances of the application that used it. This is also sensible, as
there is only one instance of a .DLL no matter how many times it is
used. HMODULE appears to be the same across different instances of
the application and .DLL. HTASK on the other hand, is the same within
an instance, whether it was obtained from the .EXE module or the .DLL.
A little explanation is in order. HINSTANCE identifies the
- 25 -
current instance. This is why every instance of an application has a
unique HINSTANCE. For every instance of an application, Windows
creates a data segment (This is usually the case as the module
definition file in an .EXE usually specifies multiple data segments).
Although undocumented, I have found that HINSTANCE is the handle to
the application s data segment.
Take figure 1a for example. I ran the test application and used
Turbo Debugger to debug it. On examination, the DS register has the
value, 0x2E47. I then inspected hInstance with the following Turbo
Debugger typecast:
(gh2fp) (unsigned) hInstance
This is equivalent to doing a GlobalLock on hInstance. The value
was, 2E47:0000. Presto! The selector value was exactly the same as
the value in the DS register.
Undocumented: HINSTANCE is the handle to the data segment.
Every .DLL has a unique HINSTANCE, but as the .DLL has only one
data segment that is shared across every application that makes use of
it, there is only one HINSTANCE value. As a rule, DLL modules are
only instanced once no matter how many times they are used. EXE
modules on the other hand are instanced once for each time they are
used.
Knowing this is they key to understanding why exported functions
in an application needs to instanced through MakeProcInstance and
those in a .DLL do not. Window functions do not need to be instanced.
A field in the windows information structure is GWW_HINSTANCE. When
messages are dispatched, the correct data segment is bound to the
window procedure via this HINSTANCE value. Subclass procedures must
be instanced before use. The HINSTANCE in the GWW_HINSTANCE field
refers to the window procedure s data segment, not the subclass
procedure s. Also, the subclass procedure must not call the window
procedure directly, but should instead use the CallWindowProc
function. CallWindowProc binds the HINSTANCE value in the
GWW_HINSTANCE field to the window procedure.
As explained in the last instalment, exported functions compiled
with Borland s smart callbacks entry/exit code automatically bind
their data segment on entry.
HMODULE refers to the file that Windows loads the code segments,
resources, etc. It can either be an .EXE or a .DLL file. Well,
actually, HMODULE is used to keep track of resources that are
associated with the .EXE or .DLL file. Take a look at the following
declaration for CLASSENTRY.
typedef struct tagCLASSENTRY
{DWORD dwSize;
HMODULE hInst;
char szClassName[MAX_CLASSNAME + 1];
WORD wNext;
} CLASSENTRY;
This structure is used by the ToolHelper function, ClassFirst.
- 26 -
We can see that there is a HMODULE field in it. When a module is
freed, all classes associated with the module will also be freed.
When a class is registered, Windows obtains the HMODULE from the
specified HINSTANCE. The class is then bound to the module specified
by the HMODULE value. This is why in Windows 3.1, only one instance
of an application is supposed to register classes. Every instance of
an application share the same HMODULE.
Tasks are like logical CPUs. Each has its own instruction
pointer and set of registers. Each task also has its own stack. The
stack is created in the application s data segment (the data segment
bound to the .EXE module). Every application on the desktop
(including the shell application -- eg. Program Manager) is a task.
In 386 enhanced mode, all tasks run in a virtual machine in protected
mode. Every DOS window is a virtual machine that runs in real mode.
Windows performs preemtive multitasking across virtual machines but
only performs cooperative multitasking across the tasks within the
protected mode virtual machine.
Figure 2
For each task, Windows creates an execution context (HINSTANCE)
associated to an .EXE module. The task may call functions in other
modules. The functions entry code will switch the execution context
to the module s. Unless the .DLL switches stacks, it executes on the
task s stack. Execution context switching is transparent to the
programmer, but incurs a penalty in CPU clock cycles.
Processors > 80286 cache their segment registers. Well, they
actually cache the descriptors pointed to by selector registers
(segment registers in real mode). As a result, when a selector is
loaded, the CPU reloads the descriptor cache, taking up valuable CPU
clock cycles. A call causing an execution context switch causes two
descriptors to be reloaded (one each for the code segment and data
segment).
When a task calls GetMessage, PeekMessage, WaitMessage, Yield or
any other API that causes a dialog box to pop up, a task switch may
occur. If there are other tasks waiting to be executed and any of
those functions were called, Windows will switch the next task in the
task queue in. Although task switches are transparent to the
programmer, a misbehaving application can prevent Windows from task
switching. As I have mentioned earlier, Windows operates a
cooperative multitasking system, meaning it is up to well-behaved
applications to give their time-slice back to Windows periodically. A
task switch incurs a lot more CPU clock cycle penanty than an
execution context switch.
Trap: Code that causes too many task switches can cause Windows to
slow down due to CPU clock cycle penalty. This penalty is usually
refered to as the multitasking overhead.
When Should HINSTANCE, HMODULE and HTASK be Used?
A number of Windows functions accept and return HINSTANCE. The
following functions accepts HINSTANCE as a parameter.
FreeModule
- 27 -
GetModuleFileName
GetModuleUsage
It may seem strange that these functions accept HINSTANCE when
they actually deal with modules. The fact is, both HINSTANCE and
HMODULE can be used. It is relatively easy to obtain the HMODULE from
a given HINSTANCE. The header file, WINDOWSX.H contains a macro
definition called GetInstanceModule that accepts a HINSTANCE and
returns a HMODULE. It passes the HINSTANCE to the low order word of
the lpszModuleName parameter in the call to GetModuleHandle. This
function returns the HMODULE. The following functions also accept
HMODULE in addition to HINSTANCE.
FreeLibrary
GetProcAddress
The following functions return HINSTANCE:
LoadLibrary
LoadModule
WinExec
As explained earlier, the GetModuleHandle function returns a
HMODULE value.
The class registration functions (RegisterClass, etc), window
functions (CreateWindow, etc) and resource functions (LoadResource,
etc) all accept HINSTANCE. MakeProcInstance and GetInstanceData
expect HINSTANCE.
To extract HINSTANCE from a given HMODULE is not straightforward, and
in some cases, impossible. This is because .EXE modules can have more
than one execution context (one per instance). Therefore, given an
.EXE HMODULE, it is still ambigiuos as to which HINSTANCE is to be
extracted. If there is only one instance of an application running,
the ToolHelper library can be used to extract the HINSTANCE.
However, it is possible to extract the HINSTANCE of a .DLL given
the HMODULE. The following function obtains the HINSTANCE of a .DLL
from a HMODULE. It retrieves the full path name of the module and
loads it. This increments the usage count of the module. The
LoadLibrary functions, in addition to loading the module, returns the
HINSTANCE. The module is then freed to decrement the usage count and
the HINSTANCE returned.
HINSTANCE GetDllModuleInstance (HMODULE hModule)
{static char path[256];
HINSTANCE hInstance;
GetModuleFileName (hModule, path, 256);
hInstance = LoadLibrary (path);
FreeLibrary (hInstance);
return hInstance;
}
It assumes that the HMODULE passed to it is a handle to a .DLL
module. GASH.ZIP contains the full source code to the above function,
in the INSTANCE subdirectory.
- 28 -
Tip: As it is easier to obtain HINSTANCE, it is recommended that it be
used in place of HMODULE where possible. It is the value passed to
the WinMain and LibMain functions. The GetWindowWord function will
return the HINSTANCE of a HWND if GWW_HINSTANCE is specified.
The GetCurrentTask function returns the HTASK for the current
application. EnumTaskWindows and PostAppMessage both accept HTASK as
a parameter. The ToolHelp library contains several functions that
allow all tasks to be enumerated.
List of Modules
In the INSTANCE\TEST subdirectory of GASH.ZIP, there is a test
application named, TESTINS.EXE. The full source code is also
contained in the same subdirectory. TESTINS is an application that
displays a list of modules in the system, their names, their usage
count and their full path name. The Update button updates the list.
As the list is not updated automatically, any modules added or removed
from the system will not show in the list. The hInstance button
displays the hInstance of the module (provided it is a .DLL) in a
messagebox.
The ToolHelp library is used to obtain the list of modules in the
system. The ModuleFirst function was called to return the first
module in Windows module list. Information is returned via the
MODULEENTRY data structure. The ModuleNext function was then called
repeatedly until every module was enumerated.
Tip: Although the ToolHelp library is meant to be used by debugging
applications, it is by no means limited to these applications.
Non-debugging applications can make use of the functions exported by
the ToolHelp library to get access into Windows internal data.
ToolHelp provides a documented set of API that allows applications to
access undocumented Windows API. To use the ToolHelp library, include
the TOOLHELP.H header file. All the exported functions are already in
the default import library (IMPORT.LIB).
When a selection is made in the listbox, another ToolHelp
function is called, the ModuleFindHandle. This function returns
information regarding a hModule in the MODULEENTRY data structure.
Information in the structure is then used to update the usage count
and path information boxes.
When the hInstance button is pressed, ModuleFindHandle is called
again. A rudimentary method was used to determine whether the
selected module is a .DLL. If it was determined that it is a .DLL,
the GetDLLModuleInstance function is called to return the hInstance of
the .DLL.
Private Dialogbox Class
The TESTINS application uses a private class modeless dialogbox
as its main window. The application is a simple one. There is no
need for the normal sort of window. A dialogbox significantly
simplifies the coding. Why did I use a private class dialogbox? In
this example, the main reason was instructional.
Tip: Private class dialogboxes provide an easy way to associate an
icon to a dialogbox. To create a private class dialogbox, define the
dialogbox template in the resource file with the optional CLASS
statement. Specify the name of the class.
- 29 -
MyDialogBox DIALOG 20, 20, 183, 193
STYLE ...
CLASS "myclassname"
BEGIN
.
.
.
END
In your application, register the class with the handle to the icon
assigned to the hIcon field of the WNDCLASS structure. Specify
DefDialogProc in the lpfnWndProc field. Important: The cbWndExtra
field must be larger or equal to DLGWINDOWEXTRA.
if (hPrev == NULL)
{wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = DefDlgProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = DLGWINDOWEXTRA;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon (hInstance, MAKEINTRESOURCE (MAIN));
wc.hCursor = LoadCursor (NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = MAINCLASS;
RegisterClass (&wc);
}
If you wish to use any of the window extra bytes, you must add the
number of extra bytes to this value. To access those bytes, start
from offset DLGWINDOWEXTRA instead of 0. Use the usual way to create
the dialogbox.
hWnd = CreateDialog (hInstance, "MyDialogBox", NULL, DlgProc);
CreateDialog create a modeless dialogbox, while DialogBox creates a
modal one. Specifying DefDialogProc in the lpfnWndProc field allows
the normal way of using a dialogbox (ie. using a DIALOGPROC
callback) to be employed. This is easier than writing a window
procedure that has DefDialogProc characteristics.
Trap: Failing to set the cbWndExtra field to DLGWINDOWEXTRA or
larger will cause the USER module to GP fault!
Trap: Failing to call IsDialogMessage in the message loop will
result in your dialog box losing the normal dialogbox keyboard
interface (ie. tab moves from control to control).
Metafiles revisited:
Last month, we looked at using metafiles saved in an
application s resources. GASH.ZIP contains the source code of a
function that loads a metafile from an instance s resources. It is in
the MET1.C file in the METAFILE subdirectory. The associated header
file, METAFILE.H, contains a #define that defines the symbol METAFILE
to the value 2000. Include this file in your resource (.RC) file. To
include a metafile, use the following line;
MetafileName METAFILE "filename.wmf"
- 30 -
where MetafileName is the name to be given to your metafile. It can
either be a #define integer symbol or a string symbol. If you use a
#define integer symbol, be sure to use the MAKEINTRESOURCE macro.
I also mentioned that Windows API cannot handle metafiles with a
placeable header. In this instalment I will show you the format of a
placeable metafile header and write a few functions that can
manipulate metafiles with placeable headers. MET2.C and MET3.C in the
METAFILE subdirectory of GASH.ZIP contains two functions that reads
and writes to disk based metafiles with placeable headers. The
functions are GetMetaFileBetter and CopyMetaFileBetter.
Metafiles with placeable headers are ordinary metafiles with an
additional 22-byte header. The following show the structure
typedef struct
{DWORD key;
HANDLE hmf;
RECT bbox;
WORD inch;
DWORD reserved;
WORD checksum;
} METAFILEHEADER;
The key field contains the magic number, 0x9AC6CDD7L. This
identifies the metafile as one with a placeable header. Ordinary
metafiles do not have an identifying signature.
Tip: The first four bytes of a placeable metafile contains the
following signature: 0x9AC6CDD7L. To identity ordinary metafiles is
not as easy. You can use the signature 0x90001L but this may not be
unique. Metafiles created by Windows version 3.0 and 3.1 contains the
value 0x300 in the following 2 bytes. This value is 0x100 for
metafiles created by earlier versions of Windows.
The hmf and reserved fields should be set to 0.
The bbox field specifies the bounding rectangle of the metafile.
It is specified in metafile units.
The inch field specifies the metafile units in units per inch.
The check sum field is an XOR value of the first 10 words in the
header. The following C statement calculates this value:
checksum = LOWORD (key) ^ HIWORD (key) ^
LOWORD (*((DWORD *) &bbox)) ^ HIWORD (*((DWORD *) &bbox)) ^
inch
The LOWORD/HIWORD key values can be replaced with the constants
0xCDD7 and 0x9AC6.
The GetMetaFileBetter function
The GetMetaFileBetter function reads a metafile just like the
GetMetaFile function in Windows API. However, GetMetaFileBetter knows
how to distinguish between an ordinary metafile and a metafile with a
- 31 -
placeable header. It accepts one additional parameter, lpMh, a far
pointer to the METAFILEHEADER structure. If a valid pointer is
specified and a metafile with a placeable header was read, the
placeable header will be copied into this structure.
GetMetaFileBetter loads the first 22 bytes of the specified
metafile. It checks it this is a placeable header. If it is not, the
GetMetaFile function is called to load the metafile. The lpMh
parameter is not filled in. If GetMetaFileBetter finds a placeable
header, memory is allocated to load the rest of the metafile, minus
the placeable header. The metafile is then loaded and the
SetMetaFileBitsBetter function is then called to convert the loaded
file into a memory metafile.
The CopyMetaFileBetter function
The CopyMetaFileBetter function copies a metafile to disk with an
optional placeable header, otherwise it functions similar to the
CopyMetaFile function in Windows API. Unlike CopyMetaFile,
CopyMetaFileBetter accepts one more parameter, lpMh, a far pointer to
the METAFILEHEADER structure. If this pointer is valid and the
structure filled in correctly, CopyMetaFileBetter will create a disk
metafile with a placeable header. The placeable header will not be
copied if a memory metafile is specified as the destination (by
specifying NULL for the lpszFile parameter).
If the lpMh parameter is not a valid pointer or if the structure
wasn t filled in correctly, or if a memory metafile is specified as
the destination, CopyMetaFileBetter calls CopyMetaFile to copy the
metafile. Otherwise, CopyMetaFileBetter opens the specified file and
writes the placeable header. It then calls the CopyMetaFile function
to create a duplicate of the metafile and passes the duplicate to
GetMetaFileBits function to retrieve a handle to the metafile data.
This data is then written to disk and the file closed.
An example usage of GetMetaFileBetter and CopyMetaFileBetter
The METAFILE\TEST subdirectory of GASH.ZIP contains an
application (TESTMET.EXE) that tests the GetMetaFileBetter and
CopyMetaFileBetter functions. TESTMET reads in GASH1.WMF, a metafile
with a placeable header, and displays it in the client area of its
window. It then saves two metafiles, GASH-YES.WMF and GASH-NO.WMF.
GASH-NO.WMF is saved from the memory metafile read from GASH1.WMF. It
is an ordinary metafile. GASH-YES.WMF is also saved from the same
memory metafile but it contains a placeable header. You can use DOS s
fc to compare GASH1.WMF and GASH-YES.WMF. GASH2.WMF is GASH1.WMF
without the placeable header. Use fc again to compare GASH-NO.WMF
with it. By the way, GASH1.WMF is this article s logo in metafile
format.
Next month...
Next month, I will write on these topics:
More metafiles DIBs in metafiles
Dealing with misbehaving metafiles
Decoding the placeable header information
Text placement in metafiles
- 32 -
Placing a metafile
Moveable windows Child windows that resize and move every time the
parent is resized
Window panes - splitting a window into resizeable panes
MDI windows Accelerator for each type of MDI windows
Dialogboxes Integer vs string resource names
Dialogbox font
Dialogboxes as child windows
Dialogboxes as MDI child windows
Coloring dialogbox controls
Keyboard interface and hotkeys
The author:
I am currently doing a PhD. degree in Electrical And Electronic
Engineering at the University Of Canterbury. My programming
experience dates back to the days where real programmers code with
Z80 machine code. For the past two years I have taken an interest in
Windows programming.
Please send any comments, questions, criticisms to me via:
email: chuah@elec.canterbury.ac.nz
or post mail: Dennis Chuah
c/o The Electronic and Electrical Engineering Department
University of Canterbury
Private Bag
Christchurch
New Zealand.
All mail (including hate mail) is appreciated. I will try to
answer all questions personally, or if the answer has general appeal,
in the next issue. If you will like to see a particular topic
discussed in up and coming issues, drop me a line. I also appreciate
discussions on topics published. If you are sending me email, please
make sure you provide me with an address that I can reach (most
internet and bitnet nodes are reachable, and so is compuserve).
- 33 -
Gdi See Gdi Do
Graphics Animation in Windows
By Bernard Andrys
Well, it's next month already and I bet all 3 of my readers are
wondering when I'll turn my "Do Nothing Program" into a way cool, time
wasting, blow'em'all'up arcade game.
But first, a VERY, VERY important correction to the last article:
As was pointed out by Michael Birk (one of the three readers),
using the PASCAL keyword for a function declaration does not make a
function pass parameters by reference. I read about the purpose of
the PASCAL keyword in an article but I really should have tried
passing parameters using the PASCAL keyword before claiming that it
works. I apologize for any confusion that it may have caused.
...and now back to blowing things up.
In this article, we'll at least get something moving. We're
going to put the missile base on the screen and make it move when you
press the cursor keys. Let's start with what we'll need to put a
missile base on the screen and make it move:
1. A missile base
2. A way to put the missile base in the window.
3. A way to link the keyboard to the drawing of the missile base.
The first requirement is pretty easy. We need to define all the
properties that a missile base has:
1. Its appearance
2. Its location
3. How many lives it has left.
4. Whether it can shoot or not. (We're only going to let it shoot one
missile at a time.)
5. Has it been hit by an invader's missile? (so we know if we should
draw an explosion instead of a missile base)
We'll work on the first two properties now and save the rest for
later.
The first property, position, is its x and y coordinates. We'll
use the variables x and y to represent the missile base's x and y
coordinates. Before we use the variables x and y, we'll need to
declare them. Declaring a variable tells the C compiler what type of
variable is going to be used. We should pick a data type for our
variables x and y that is big enough to handle the largest values that
might be assigned to x and y.
So how big are the data types that we have to work with?
Data Type Size
char -128 to 127
int -32,768 to 32,767
unsigned char 0 to 255
unsigned int 0 to 65,535
- 34 -
unsigned int 0 to 4,294,967,295
float 3.4E38
double 1.7xE308
void nothing
There are a lot of other data types supported by C compilers such
as long int and short int but considering that Win32 and NT are just
around the corner you probably don't need the confusion of learning
X86 segmentation now. (Especially since this IS a beginner's column.)
Windows has its own data types as well. For example HBITMAP and
HINSTANCE are two data types that are specific to Windows. You would
declare a variable as HBITMAP if the variable was going to contain a
handle to a bitmap. A handle is a number that a program uses to refer
to a file, bitmap, or some other object. You don't normally have to
care about the size of Windows' own data types like HBITMAP or
HINSTANCE because Windows' own data types are normally used as return
values or parameters for Windows' own functions. For example, if we
used a function called LoadBitmap() it would return a variable of type
HBITMAP. The HBITMAP variable would then be used where ever we want
to refer to the actual bitmap.
EXAMPLE:
MyFunction()
{
/* declare a variable called mybitmap which is data type HBITMAP */
HBITMAP mybitmap;
/* Window's function LoadBitmap() returns an HBITMAP variable */
mybitmap = LoadBitmap(hInstance, "bitmap");
}
Actually, Windows doesn't have its own data types. Data types
like HBITMAP are defined in the file windows.h as an unsigned integer.
You declare the variable as HBITMAP instead of unsigned integer
because the size of HBITMAP might (and will) change under NT or maybe
even in future versions of Windows like Windows 4.0. The size of
HBITMAP could even change for each CPU that the program is compiled
for (this is an NT consideration again).
Now that we know what data types are available, we can pick a
data type for x and y. Since the window is 400 pixels high and 400
pixels wide, we should define x and y as type int. We'll probably
have to use the position of the missile base in many places throughout
our code so it's best that we define x and y as global variables by
defining them in the header file "invid.h".
The second property, its appearance, is defined by a bitmap that
we'll have represent the missile base. You can create the bitmap of
the missile base using any program that can create .bmp files. We're
going to use 16 color bitmaps so be sure to save the file in that
format. Take a look at the bitmap, base.bmp, that I created to
represent the missile base.
Notice that the bitmap has black pixels to its right and left.
Since the missile base only moves right and left, and the background
is solid black, we can cheat in creating the bitmap animation. We
don't have to use a bitmap mask to draw the bitmap without disturbing
the background. We also don't have to bother redrawing the background
- 35 -
when the base is moved. As long as we move the base to the right or
left 5 pixels or less at a time (There's a 5 pixel black border on
each side of the base.), drawing the base covers over the previous
base bitmap that was on the screen. This way we can just draw the
bitmap to the screen directly.
To use a bitmap in a Windows program you first need to define it
in your program's .rc file. Here is the contents of the invid.rc
file:
#include "windows.h"
invid ICON invid.ico
base BITMAP base.bmp
We've added the missile base's bitmap (base.bmp) to the end of
the .rc file after the icon file. The first entry, base, will be what
we call the bitmap file in the program's source code. The second
entry, BITMAP, tells the resource compiler that this is a bitmap
resource. The last entry, base.bmp, is the name of the bitmap file
that we want to load. Now our program will contain the bitmap as a
resource that can be loaded and used by our program. To use the
resource, you call the function LoadBitmap(Program_Instance, "bitmap
name"). The first parameter to LoadBitmap() is the handle of the
program instance. So we'll need to save the hInstance variable that
is passed to WinMain() by assigning it to our own variable hInst.
That way we can use hInst when we call the LoadBitmap() function. The
second parameter is the name of the bitmap that we defined in the .rc
file. The LoadBitmap function returns a variable of type HBITMAP that
we use whenever we want to refer to the bitmap. I added a variable
called hbBase of type HBITMAP in our header file invid.h.
Fast and Friendly Animation
Now that we've got our missile base, how do we display it on the
screen? There are two ways we can go about it:
1) Display the missile base at its position every time we get a Timer
message.
2) Display the missile base at its position whenever it moves.
The first method would be ideal for multitasking friendliness.
However, this method is too slow. At best, windows can only provide
18 timer messages per second. I originally thought that this would be
fast enough. After all, TV is at 30 frames/sec, movies run at 24
frames/sec and Microsoft Video files look fine at 15 frames/sec.
Unfortunately it didn't work out that way. 18 frames per second is
just too slow for arcade game speed. We want the animation to be as
smooth as possible. That means that if the user tapped the right
cursor key quickly, the missile base would move 1 pixel to the right.
If the user held the cursor key down, the base would move 18
pixels/second across the screen. However, our window is currently 400
pixels wide. So it would take over 22 seconds to move the base from
one end of the screen to the other. Twenty-two seconds is a LONG time
in arcade style action games. This method just won't work with
Windows only sending 18 timer messages per second. Well, we could
make it work if we added motion blur but I'd rather not try and tackle
- 36 -
motion blur ...yet. We could make this method work if we could get
Windows to send us more than 18 timer messages per second. There are
ways of doing this using the multimedia timer services but they seem
fairly complicated to me. (The real reason is that I've been too lazy
to upgrade from Quick C which only supports the Win 3.0 API.)
The second method is much easier to deal with but creates its own
problems. Instead of having our program only do something in response
to a message, we can write our program to run continuously.
Unfortunately we need to be careful with this method because we want
our program to be friendly to other Windows programs and not stop
every other Windows program just because ours is running. To do this
we can use Windows' PeekMessage() function to see if Windows has
anything else to do. If it does, we'll let Windows take care of the
other programs before coming back to our program. This way our
program will run continuously as long as no other program has things
to do. An obstacle to using PeekMessage is that you shouldn't keep
your program in a PeekMessage loop because it keeps Windows from
posting idle messages. Another problem with this method is that since
our program runs as fast as the CPU will allow, we will have to do our
own timing to keep the animation rate constant no matter what CPU
we're running on.
I created two sample programs that demonstrate the difference
between timer based animation and peek message based animation. The
code in the two programs is essentially the same. Tball.exe creates a
timer that sends 18 timer messages per second (the maximum under Win
3.1).
Condensed code:
int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR
lpCmdLine, int nCmdShow)
{
MSG msg;
if (!hPrevInstance) InitApplication(hInstance));
InitInstance(hInstance, nCmdShow)
while (GetMessage(&msg, NULL, NULL,NULL))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (msg.wParam);
}
LONG FAR PASCAL WndProc(HWND hWnd, unsigned message,
WORD wParam, LONG lParam)
{
switch (message)
{
case WM_CREATE:
// create a timer
SetTimer (hWnd, 1, 1, NULL);
// get handle to bitmap
hbBall=LoadBitmap(hInst, "ball");
break;
case WM_TIMER:
- 37 -
/* if a timer message arrives, draw the ball */
DrawBall();
break;
case WM_DESTROY:
KillTimer(hWnd, 1);
DeleteObject(hbBall);
PostQuitMessage(0);
break;
default:
return (DefWindowProc(hWnd, message, wParam, lParam));
}
return (NULL);
}
void DrawBall(void)
{
MoveBall();
DrawBitmap(hbBall, x, y);
EraseOldBall();
return;
}
If you run Tball.exe, you'll notice that the ball moves fairly
quickly and smoothly across the screen. Unfortunately this is as fast
as you can get with timer based animation without making the animation
jerky. The menu selection Speed allows you to control change in
position of the ball per timer message. AnimeStep=1 means that the
ball moves 1 pixel on every timer message. AnimeStep=50 means that
the ball moves 50 pixels on every timer message. Changing how far the
object moves per timer message is the only way to effectively control
the speed of an object in timer based animation.
In contrast, if you run Pball.exe, you'll notice that the
animation is smoother and faster. The code is different in one area,
the message loop. Instead of the while(GetMessage()) loop, we have a
loop that runs as long as a WM_QUIT message is not received.
PeekMessage() checks to see if there are any messages waiting to be
dispatched by Windows. If there aren't any messages waiting, the
function DrawBall() is run. Notice that WndProc() doesn't do much.
The Setup, Control, and Exiting of the Window is in WndProc, but all
the animation is performed whenever Windows isn't doing anything else.
int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
if (!hPrevInstance) InitApplication(hInstance));
InitInstance(hInstance, nCmdShow))
while (TRUE)
{
if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT) break ;
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
- 38 -
}
else
{
/* if no messages waiting, run DrawBall() */
DrawBall() ;
}
}
return (msg.wParam);
}
LONG FAR PASCAL WndProc(HWND hWnd, unsigned message, WORD wParam, LONG
lParam)
{
switch (message)
{
case WM_CREATE:
hbBall=LoadBitmap(hInst, "ball");
break;
case WM_DESTROY:
DeleteObject(hbBall);
PostQuitMessage(0);
break;
default:
return (DefWindowProc(hWnd, message, wParam, lParam));
}
return (NULL);
}
void DrawBall(void)
{
MoveBall();
DrawBitmap(hbBall, x, y);
EraseOldBall();
return;
}
The menu item Speed works the same in Pball as it did in Tball.
AnimeStep controls how many pixels the ball is moved in every
animation step. But instead of 18 animation steps per second, you
have as many animation steps as your CPU can crank out before Windows
has a message to process. If Windows has a message to process, the
ball stops moving while the message is handled. The ball will then go
back to moving as soon as there are no more messages. Fortunately,
messages are handled so quickly that you don't see any noticeable
pause in the animation of the bouncing ball.
So let's apply this PeekMessage animation technique to The Game:
/* invid.c */
#include <windows.h>
#include "invid.h"
/*---------- Global Variables --------*/
/* In the real source code I keep my global variables in "invid.h" but
it's easier for you to read the source code if I put them here. */
- 39 -
/* Declare a structure called decodeWord that will be used by WinProc
to run a function in response to a message */
struct decodeWord {
WORD Code;
LONG (*Fxn)(HWND, WORD, WORD, LONG); };
/* An array called messages made up of decodeWord structures is
created. The array lists the Windows Message and the function that
will be called when that message is received. I've added two
messages, WM_KEYDOWN and WM_KEYUP. When WM_KEYDOWN or WM_KEYUP is
received, the function DoKeydown or DoKeyUp is run. */
struct decodeWord messages[] = {
WM_KEYDOWN, DoKeydown,
WM_KEYUP, DoKeyup,
WM_CREATE, DoCreate,
WM_DESTROY, DoDestroy,} ;
/* Declare a character string that contains the name of the program.*/
char szAppName [] = "Invid";
/* Declare a variable called hInst that contains the data for a
particular instance of the program. */
HANDLE hInst;
/* Declare a variable called hWnd that contains a handle to the
programs window. */
HWND hWnd;
/* Declare an integer called AnimeStep. AnimeStep is how many pixels
a bitmap should be moved during animation. */
int AnimeStep = 2;
/* Declare two variables of type BOOL (boolean). That means that the
variables can be a 1 or a 0. The variables fRightKeyState and
fLeftKeyState will be set by the functions DoKeyUp and DoKeyDown. The
variables will then be used by other functions so that they can know
the state of the right and left cursor keys. (I could have used the
Windows function GetAsyncKeyState() instead of waiting for a keypress
message to be sent by Windows. But I wanted the program to be written
in the standard Windows programming style of waiting for a message.)
*/
BOOL fRightKeyDown = FALSE, fLeftKeyDown = FALSE;
/* Decalaring a structure called BaseStruct that will contain
everything about the missile base. I could have just as easily
declared x, y, and Bmp as separate variables. Putting them into a
structure groups related variables under one title and makes it easy
to remember what variable does what. */
struct BaseStruct {
/* Declare an integer called x that will contain the base's x
coordinate */
int x ;
/* Declare an integer called y that will contain the base's y
coordinate */
int y ;
/* Declare a variable called Bmp that contains a handle to the bitmap
of the missile base. */
HBITMAP Bmp ;
};
- 40 -
/* Declare a Structure called Base of type BaseStruct (defined above).
You can now access the missile base's variables x, y and Bmp by using
Base.x, Base.y, and Base.Bmp. */
struct BaseStruct Base;
/*------------ WinMain ------------*/
int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance, LPSTR
lpszCmdParam, int nCmdShow)
{
MSG msg ;
hInst = hInstance;
if (!hPrevInstance) InitApplication(hInstance);
InitInstance(hInstance, nCmdShow);
/* This is the old message loop from the last program.
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
*/
/* This is the new message loop (from Petzold). Instead of calling
GetMessage() to retrieve a message from Windows, this message loop
calls PeekMessage to see if there are any messages waiting. If the
waiting message is a WM_QUIT message then break out of the loop and
end our program. If the waiting message is not a WM_QUIT message then
translate and dispatch the message. If there are no waiting messages
then run our function DoInv(). This message loop will allow other
Windows programs to run at the same time but it will also keep idle
messages from being generated. We'll have to fix it in a future
article to make it more multitasking friendly. */
while (TRUE)
{
if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT) break ;
TranslateMessage (&msg) ;
DispatchMessage (&msg) ; }
}
else
{
DoInv() ;
}
return (msg.wParam) ;
}
/*---------- InitApplication ---------*/
BOOL InitApplication(HANDLE hInstance)
{
WNDCLASS wndclass;
wndclass.style = 0 ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (hInstance, szAppName) ;
- 41 -
wndclass.hCursor = LoadCursor (hInstance, IDC_ARROW);
wndclass.hbrBackground = GetStockObject (BLACK_BRUSH);
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
return(RegisterClass (&wndclass));
}
/*------------ InitInstance ----------*/
BOOL InitInstance(HANDLE hInstance, WORD nCmdShow)
{
hWnd = CreateWindow (szAppName,
"Invid",
WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU,
CW_USEDEFAULT, CW_USEDEFAULT,
WINDOW_WIDTH, WINDOW_HEIGHT ,
NULL,
NULL,
hInstance,
NULL) ;
ShowWindow (hWnd, nCmdShow) ;
UpdateWindow (hWnd) ;
return (TRUE);
}
/*------------- WndProc ------------*/
long FAR PASCAL WndProc (HWND hWnd, WORD wMsg, WORD wParam, LONG
lParam)
{
int i;
for(i=0; i < dim(messages); i++)
{
if(wMsg == messages[i].Code)
return((*messages[i].Fxn)(hWnd, wMsg, wParam, lParam));
}
return(DefWindowProc(hWnd, wMsg, wParam, lParam));
}
/*------------- DoCreate -----------*/
/* DoCreate() is called when our window receives a WM_CREATE message
at startup. */
LONG DoCreate(HWND hWnd, WORD wMsg, WORD wParam, LONG lParam)
{
/* Call a function called BaseInit that will initialize the variables
in the missile base structure Base. */
BaseInit();
return 0;
}
/*------------ BaseInit -----------*/
/* This function initializes the variables and loads the missile
base's bitmap. */
void BaseInit(void)
{
Base.x = 40;
Base.y = WINDOW_HEIGHT - 80;
Base.Bmp = LoadBitmap(hInst, "base");
}
- 42 -
/*------------ DoDestroy ----------*/
/* DoDestroy is called when our program receives a WM_QUIT message */
LONG DoDestroy(HWND hWnd, WORD wMsg, WORD wParam, LONG lParam)
{
// Delete the handle to the missile base's bitmap
// before exiting. (otherwise Windows system
// resource's won't be released.
DeleteObject(Base.Bmp);
PostQuitMessage (0) ;
return 0 ;
}
/*----------- DoKeyDown ---------*/
/* DoKeyDown is run whenever our program receives a WM_KEYDOWN
message. */
LONG DoKeydown(HWND hWnd, WORD wMsg, WORD wParam, LONG lParam)
{
switch (wParam) {
case VK_RIGHT :
fRightKeyDown = TRUE ;
break ;
case VK_LEFT :
fLeftKeyDown = TRUE ;
break ;
}
return 0 ;
}
/*---------- DoKeyUp ------------*/
/* DoKeyUp is run whenever our program receives a WM_KEYUP message.*/
LONG DoKeyUp(HWND hWnd, WORD wMsg, WORD wParam, LONG lParam)
{
switch (wParam) {
case VK_RIGHT :
fRightKeyDown = FALSE ;
break ;
case VK_LEFT :
fLeftKeyDown = FALSE ;
break ;
}
return 0 ;
}
/*----------- DoInv -------------*/
/* This function runs whenever Windows isn't doing anything else */
void DoInv(void)
{
BaseAnimation (hWnd);
return ;
}
/*----------- BaseAnimation -------*/
/* This function draws the bitmap of the base at a new position based
on whether the cursor keys are pressed */
void BaseAnimation (void)
{
/* Move the x coordinate of the missile base based on which cursor key
is pressed. Base.x += AnimStep is shorthand forBase.x = Base.x +
- 43 -
AnimeStep */
if (fRightKeyDown) Base.x += AnimeStep;
if (fLeftKeyDown) Base.x -= AnimeStep;
/* Keep the missile base on the screen */
if (Base.x < 5) Base.x = 5;
if (Base.x > (WINDOW_WIDTH-50)) Base.x = WINDOW_WIDTH-50;
/* Another Petzold function slightly modified to make it simpler to
use. (If you havn't bought Programming Windows by Charles Petzold yet,
stop reading this and go to your local bookstore and buy it now!) The
function takes three arguments. The first argument is a handle to the
bitmap that will be drawn. The second and third parameters are the x
and y position of the bitmap to be drawn. */
DrawBitmap (Base.Bmp, Base.x, Base.y);
}
/*----------- DrawBitmap ------------*/
/* This is where all the action takes place. It's great for getting a
bitmap on your window as simply as possible. Unfortunately it has
performance problems when you try and use it for all your graphics
work. (This might be why Windows doesn't have this function built in)
*/
void DrawBitmap (HBITMAP hBitmap, int xStart, int yStart)
{
/* Declare a handle to a device context. A handle for a Device
Context is needed to draw to a window. */
HDC hdc;
/* Declare a variable bm of type BITMAP. It is used to store
information about a bitmap such as its size and colordepth. */
BITMAP bm ;
/* Declare a handle to a device context. This handle will be for a
block of memory that will be used as storage for the bitmap that will
be drawn. */
HDC hdcMem ;
/* Get a handle for a device context for our window. */
hdc = GetDC(hWnd);
/* Get a handle for a device context to a block of memory. The memory
context will be based on the device context of our Window. */
hdcMem = CreateCompatibleDC (hdc) ;
/* Put the bitmap into the block of memory that was created above. */
SelectObject (hdcMem, hBitmap) ;
/* Get information about the bitmap and put it in the structure bm */
GetObject (hBitmap, sizeof (BITMAP), (LPSTR) &bm) ;
/* The BitBlt function copies the bitmap in memory (hdcMem) to the
screen (hdc) */
BitBlt (hdc, xStart, yStart, bm.bmWidth, bm.bmHeight,
hdcMem, 0, 0, SRCCOPY) ;
/* Free up the resources we used before leaving */
DeleteDC (hdcMem) ;
ReleaseDC (hWnd, hdc);
}
When you run the program, it will put a small bitmap on the
screen that moves back and forth when you press the cursor keys. If
you're running it on a fast 486, you'll notice that it's too fast to
be controllable in a game. Fortunately slowing down a program is
always easier than speeding one up.
You now have the basics of an arcade game: Fast graphics with
- 44 -
user interaction. In the next issue, we'll add the invaders and cover
BitBlt'ing in detail.
I hope you enjoyed my second article. I spent a lot of time on
formatting details for both the .hlp file and the .txt file. In the
.hlp file I added bitmaps and linked bitmaps to run the sample
programs while you're reading the .hlp file. I also spent a lot of
time on the format of the source code with details such as using bold
for the code and blue highlights for the start of functions. If you
like this formatting, (or don't like it!) send me or the editors
feedback.
About the author:
(short version)
SWM 25 ISO SWF. PC Compatible, no experience necessary.
(long version)
Bernard Andrys is a engineer at CSI, an ISDN networking development
company. He holds a bachelor's degree in Mechanical Engineering from
the University of Maryland at College Park. He can be reached through
the Internet at andrys@csisdn.com or on the Windows Programming
Journal BBS.
(longer version)
I was born in the house my father built. ...No wait, that's someone
else. umm... Ok... I bet you're wondering what a Mechanical Engineer
is doing working for a networking company and writing Windows program
in his spare time. Well I'm wondering too. If you find out, write
me. ...and another question. Is it the company I keep or do all
programmers like The Hitchhiker's Guide to the Galaxy and Monty
Python?
- 45 -
Windows Hooks
By David S. Browne
What is a Windows Hook ?
Ever want to get control just when Windows processes that
keydown, or at that mouse move ? Well, Windows Hooks are for you ! A
hook is really nothing more than Windows making a call to a DLL at
various points in Kernel, User, GDI, etc. There are some gotchas,
though, in using a hook and keeping Windows alive to process work, and
that's what I hope to show here. There are three types of common
hooks in Windows applications these days; 3.0 style hooks, 3.1 style
hooks and Dynamic Intercept of Windows API functions for your own evil
doings. The first two of these are somewhat documented in various
Microsoft and OA ('Other Authors') books. The last is not documented
by Microsoft at all, but has been shown in various trade publications.
We will cover all three of these, in reverse order. We will present a
real application usage of hooks when we look at the documented 3.0/3.1
hook functions. The application we will develop is a windows pop-up
(I know, I'm still stuck in DOS !) that will allow you to activate it
with any user defined hotkey and it will allow you to END or KILL any
running window in your system. The problem with PROGMAN is that in
many cases its END-TASK simply doesn't do the job. The target window
must be reading its message queue to see the WM_QUIT message. The
KILL function will call a nice new TOOLHELP debugging function to
force kill any window, even if its not got its ear to the queue, so to
speak.
Dynamic Intercept Of Windows API functions
Dynamic Intercept of Windows API functions is not the main basis
of this article but I wanted to touch on the subject a bit before
getting on with the real meat. When you issue a GetMessage or
BeginPaint API call in your Windows program, it's nothing more than a
call to a Windows system DLL to process this function. It is possible
to intercept this to do any front end processing (or for that matter
back end after the real API has finished playing) that you wish.
Windows provides a function ("GetProcAddress") that will return the
memory address of any function call you wish to look at. With this
information you can store a jump at the DLL routine entry to your
routine.
It's actually a bit more complicated than this as Windows has a
habit of discarding code segments when it's short of memory and
reloading fresh copies from disk as needed. So for this kind of
dynamic intercept it's also necessary to use the TOOLHELP DLL to
install a NotifyRegister function so that you see the segment reloads
to re-apply your hook. If this is not done, then all will work fine
until Windows decides that a bit more memory is in order, purges some
code segments (that your hook is in !) then reloads a fresh copy and,
Presto! Chango! NoHookO ! It's really not as complicated as it all
sounds; a very good example of this was used in the May 1993 edition
of Windows/DOS Developer's Journal. If you want to start intercepting
Windows API functions then I recommend you read, then re-read, then
sleep read the article ! Now to the beef !
- 46 -
Windows 3.0 and 3.1 Hooks
There are two API's to set a hook in Windows, the 3.0
SetWindowsHook and the 3.1 SetWindowsHookEx. The basic change is
that the 3.0 SetWindowsHook is system wide, while the 3.1 version
allows a hook to be JUST for a given instance or task. This allows
you to develop that Keyboard Hook you always wanted, but were afraid
of because of the overhead of getting called for EVERY SYSTEM KEYBOARD
EVENT. The example code we present here though uses a system-wide
keyboard hook (in for a penny, in for a pound) so no matter where the
current focus is we see the keydown and keyup. In addition to the
hook function ID the main param you must provide on both of these
calls is the address of the function that you are providing to process
these hooks. This function must reside in a DLL. As with the 3.0
hook or a system wide 3.1 hook, it can be called in the instance of
any task in the system. A DLL is required so as to be available to
not just the API calling task.
There are twelve hooks you can set with these functions:
WH_CALLWNDPROC - WNDPROC Filter Hook for all SendMessage API
Call's
WH_CBT - Computer Based Training Filter
WH_DEBUG - Called before any other hook
WH_GETMESSAGE - Called when an application issues a GetMessage
API
WH_HARDWARE Called for any non-keyboard or non-mouse hardware
message
WH_JOURNALPLAYBACK - Allows user to substitute mouse and keyboard
input
WH_JOURNALRECORD - Allows user to save mouse and keyboard
messages
WH_KEYBOARD - Called when any key is struck.
WH_MOUSE - Called for EVERY mouse movement in the system. (Did
someone say Pentium ?)
WH_MSGFILTER - Called for user messages to any Dialog, Menu or
Scroll Bar Window
WH_SHELL - Called whenever a new top level window is created or
destroyed.
WH_SYSMSGFILTER - Called for system messages to any Dialog, Menu
or Scroll Bar Window
Some documentation I have seen does not list the WH_SHELL hook as
valid for SetWindowsHookEx. It is, it works, I've used it. Now for
the actual API calls:
3.0
STATIC HHOOK hook1 = NULL;
.
.
.
hook1 = SetWindowsHook(WH_KEYBOARD, (HOOKPROC)KeyHook);
.
.
DWORD CALLBACK KeyHook(int iCode, WPARAM wParam, LPARAM lParam)
{
WhoKnowsOrCares();
?????????
- 47 -
}
3.1
STATIC HHOOK hook1 = NULL;
.
.
.
hook1 = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)KeyHook,
hInstance, NULL);
.
.
DWORD CALLBACK KeyHook(int iCode, WPARAM wParam, LPARAM lParam)
{
WeNowKnowAndCare();
CallNextHookEx(hook1, iCode, wParam, lParam);
return(0);
}
Simple right ? Really it is ! Honest ! Have I ever lied to you ?
Now to discuss an actual application usage for all this.
As I said, when you pop up Windows TASKMAN with the ever faithful
Ctrl-Esc we have an END-TASK Button. Ever had a case where it didn't
end that misbehaving task? Well that's because all it does is send a
WM_CLOSE message to the application. If it is processing its messages
normally it will see this message then terminate. I don't know about
you but whenever I use that button the application is normally
stalled! If I could end the application I would have done it by
selecting close from the Application itself! OK, now we understand
the problem. Solution time. We will implement a WH_KEYBOARD hook to
scan for any ALT-S key sequence and if we see it pop up a window that
will list all active top level windows on it and allow the user to END
the task (same as the dreaded TASKMAN does!) or KILL the task by a
call to the new 3.1 TOOLHELP DLL TerminateApp() function. The program
is implemented in two separate modules WINSWAT, the main window
procedure and SWATDLL the DLL with the keyboard hook function. If you
look at WINSWAT it will look as any normal Windows C program does, it
has a WinMain function that sets up a main window for the application
and it has a WndProc for all the real processing. During the
WM_CREATE processing, we make a call to InitialiseSwat to SWATDLL to
setup the keyboard hook. The code in SWATDLL looks like this:
void WINAPI _export InitialiseSwat(BOOL fAction, HWND hwnd)
{
if (fAction)
{
OutputDebugString("SWATDLL: Pre--SetHook\n");
hKhook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)
KeyboardHook,
hInstance, NULL);
OutputDebugString("SWATDLL: Post-SetHook\n");
hTASK = GetWindowTask(hwnd);
hHWND = hwnd;
}
else
{
- 48 -
UnhookWindowsHookEx(hKhook);
hKhook = NULL;
hHWND = NULL;
hTASK = NULL;
}
}
This sets up a hook callback to KeyboardHook for keyboard events
from any task in the system (The last NULL param says all tasks, but
you could substitute a task value here if you ONLY wanted keyboard
events for that task). The HWND param we save here is so that later
in the actual keyboard hook code we can PostMessage to that window.
The last part of the code is for termination to remove our hook.
After this processing is complete, WINSWAT simply hides itself with a
ShowWindow call and waits to be woken up with a message from SWATDLL.
If you look at the KeyboardHook code in SWATDLL you will see that
we check for our key, the S key, then we make sure the ALT key is on.
If all this is true we PostMessage a message to WINSWAT so that it
will wake up. Take a look:
DWORD CALLBACK KeyboardHook(int nCode, WPARAM wParam, LPARAM lParam)
{
static BOOL fPost;
BYTE iBit;
WORD iWord;
static UINT iQky = 83;
if (nCode >= 0)
if (wParam == iQky)
{
OutputDebugString("SWATDLL: Key Hook Hit\n");
iWord = HIWORD(lParam);
iBit = HIBYTE(iWord);
if (iBit & 0x20)
{
fPost = PostMessage(hHWND, WM_COMMAND, WAKESIGL,
0);
return 1;
}
}
CallNextHookEx(hKhook, nCode, wParam, lParam);
return 0;
}
When the WAKESIGL message is received in the WndProc WM_COMMAND
processing section, it simply changes the Main Window to SHOW status,
updates the listbox with all top level window names and then waits for
a command. If a KILLBUTN is received, it is used the TerminateApp()
function to really SWAT the thing out of Windows!
hTask = GetWindowTask(WorkHwnd);
TerminateApp(hTask, NO_UAE_BOX);
As you can see it's really not at all hard to use hooks. They
can add functionality to any application when used in the correct
- 49 -
manner. Don't substitute hooks, though, for things that could be
easily done with normal Windows messages like WH_KEYDOWN, etc. Hooks
can add overhead to your system, and that's something no one wants.
Used correctly, however, they can add a multitude of features to your
application.
All the source code, H files, Icons, Definition file etc. are
included with this WPJ edition, take a look at it and get Hooking!
- 50 -
Shared Global Memory
By Dennis Chuah
In this article I will present a way to implement shareable
global memory in Windows 3.1. It is also the aim of this article to
demonstrate two different methods of detecting whether an application
has been loaded or not. Although the code is written in C, aiming for
the Borland C++ compiler, the binaries produced and the associated
header file can be used by any other C compiler that supports
programming in Windows. It is also possible to use the libraries with
Turbo Pascal or Borland Pascal with the proper import definitions. I
am not an expert in Pascal and I do not own a Pascal compiler. So I
hope someone who is better versed in Pascal can write a unit that
allows the libraries to be used in Pascal programs.
First, let me introduce a few concepts of memory allocation in
Windows 3.1. Windows 3.1 only runs in protected mode and therefore
most of the memory allocation practices that were once used in real
mode are now obsolete. Prior to version 3.0[1], Windows runs in real
mode. The API inherits a number of real mode features from previous
version of Windows. In real mode, Windows needs to perform memory
moves to accommodate a multitasking environment. This is achieved
through handles. When a block of memory is allocated, a handle is
assigned to it. The owner can request that the block of memory be
locked in memory (meaning it will not be moved by Windows). The
locking process also retrieves a pointer to the block of memory. When
the owner no longer needs to access the block of memory, it can tell
Windows to unlock it. Windows is then free to move the block of
memory around. The next time the owner needs to access the block of
memory, it locks it and retrieve another pointer. The value of this
pointer is not necessary the same as the previous pointer it retrieved
(Windows may have moved the block of memory elsewhere). This
complicated process is necessary because in real mode, pointers point
to physical addresses. The only way for Windows to inform an
application that a block of memory it owns has been moved is by
passing a new pointer to it. The locking process ensures that Windows
does not move memory that is still being actively used.
In protected mode however, pointers point to logical address.
Each pointer, instead of having a segment:offset address, has a
selector value and an offset address. The selector points to an entry
in a global table (The Global Descriptor Table -- GDT) maintained by
Windows. When a memory access occurs, the CPU translates the logical
address in a pointer to physical address using the GDT. This process
is transparent to software. When Windows running in protected mode
needs to move blocks of memory, it only needs to update the GDT.
Locked memory can also be moved as the CPU translates memory access
via the GDT transparently. Therefore the process of allocating a
handle, locking it and unlocking it becomes obsolete. The owner of a
block of memory only needs to maintain a pointer to it. The following
table illustrates the differences in the process of allocating memory
under different modes.
Real mode
Protected mode
Allocating Memory
handle = GlobalAlloc...
- 51 -
handle = GlobalAlloc...
ptr = GlobalLock (handle);
Accessing memory
ptr = GlobalLock (handle);
*ptr = ...
GlobaalUnlock (handle);
*ptr = ...
Freeing memory
GlobalFree (handle);
handle = LOWORD (GlobalHandle
(SELECTOROF (ptr)));
GlobalUnlock (handle);
GlobalFree (handle);
The extra work in maintaining handles in protected mode is a
legacy inherited from real mode. It is there for backward
compatibility. For a more detailed discussion of Windows memory
allocation I suggest C. Petzold, Programming Windows -- 2nd Ed.,
(1990) Microsoft Press. For more information in the Intel 80x86
protected mode memory management, consult Intel s, 80386 System
Software Writer's Guide , (1988) Intel, Intel's, 80386 Programmer's
Reference Manual, (1988) Intel, and J Crawford & P. Gelsinger,
Programming the 80386, (1987) SYBEX Inc.
Shared Memory
Normally global memory allocated to an application belongs to the
application and no other application can access it. Should any other
application happen to access it, a GP fault will occur. To
accommodate DDE, Windows 3.0 enables blocks of global memory allocated
with the GMEM_DDESHARE flag to be shared by all applications.
According to the API documentation, memory allocated with this flag is
automatically freed when the owner terminates.
Now, suppose we want to allocate a block of memory that can be
shared between two or more applications. One application can allocate
it with the GMEM_DDESHARE set and pass the handle/pointer to the
others. This is fine, provided the owning application does not
terminate before any of the others that make use of the block of
global memory. In the user friendly environment of Windows, the
programmer cannot guarantee the life of an application. In fact, the
programmer should not write code that exhibits this behaviour.
Imagine the frustration of the user of not being able to close an
application if it was the first one in a group to be opened!
To get around this problem, we must allocate shareable global
memory that can live longer than the application that allocated it.
I wrote a .DLL and a background application that only allocates
shareable global memory. It involves writing an application that
always remain active, but is hidden from the user. All requests to
allocate shareable memory is made through the .DLL and carried out by
the hidden application. This way, every block of shareable memory
that is allocated remains allocated until explicitly freed or when
Windows is terminated. This means that an application can request the
- 52 -
.DLL to allocate a block of memory on its behalf, pass the pointer to
other applications and terminate. When the block of memory is not
required anymore, it can be freed.
More specifically, a .DLL when called for the first time can
allocate shareable memory and store the pointer away. When it is
called again (probably from another application), it can reuse the
block of memory, without causing a GP fault.
The hidden application
Included is a file named SHAREMEM.ZIP. The source code of the
hidden application (_MALLOC.EXE) is found in the root directory.
Unzip this file with the -d flag to preserve its directory structure.
The binaries, ie. the .DLL and the .EXE must be placed in the Windows
directory, the System directory or any other directories pointed to by
the PATH environment variable.
There are two sections in _MALLOC.EXE. The first initialises and
registers the hidden window class. It also checks if there is another
instance of itself in memory. It will not load if it found one. The
second section contains a hidden window (a message target) that
accepts requests to allocate shareable memory.
_MALLOC.EXE check whether there is another instance of itself via
the call to RegisterClass. RegisterClass fails if an application
registers more than once across instances. This is a simple way to
check if another instance exist. The hidden window is registered as a
global class with the CS_GLOBALCALSS style. When RegisterClass
attempts to register a second class instance of a global class, it
will fail. This prevents the user from accidently running a second
instance of MALLOC.EXE.
The .DLL
The .DLL (MALLOC.DLL) interfaces MALLOC.EXE to applications that
make use of it. It is located in the MALLOC directory of
SHAREMEM.ZIP. Like _MALLOC.EXE, _MALLOC.DLL contains two sections.
The first section performs initialisations and checks if _MALLOC.EXE
has been loaded. If it not, it will be loaded. If MALLOC.DLL cannot
load MALLOC.EXE, the initialisation will fail. The second section
contains the shareable memory allocation API.
MALLOC.DLL determines whether _MALLOC.EXE is loaded by first
registering a pivate Windows message. It then creates a hidden test
window and broadcasts the message to all top level windows. The
wParam of the message contains the handle of the test window.
HWND hWnd;
HINSTANCE hInst;
#define MAL_ACK 1
#define TEST_ID 0x41A5
.
.
.
static void _gethwnd (void)
- 53 -
{HWND hwndTest;
hwndTest = CreateWindow (TEST_CLASS, "", WS_OVERLAPPED,
0, 0, 0, 0, NULL, NULL, hInst, NULL);
if (hwndTest == NULL) return;
ShowWindow (hwndTest, SW_HIDE);
SendMessage (HWND_BROADCAST, uTestMsg, (WPARAM) hwndTest,
MAKELPARAM (0, TEST_ID));
DestroyWindow (hwndTest);
if (hWnd == NULL)
{WinExec ("_MALLOC.EXE", SW_HIDE);
hwndTest = CreateWindow (TEST_CLASS, "", WS_OVERLAPPED,
0, 0, 0, 0, NULL, NULL, hInst, NULL);
if (hwndTest == NULL) return;
ShowWindow (hwndTest, SW_HIDE);
SendMessage (HWND_BROADCAST, uTestMsg, (WPARAM) hwndTest,
MAKELPARAM (0, TEST_ID));
DestroyWindow (hwndTest);
} // endif
} // end gethwnd
The _gethwnd function first tests to see if _MALLOC.EXE is
loaded. If not, it will attempt to load it. It then tests again to
see if _MALLOC.EXE is loaded. If the second check fails, it assumes
that it has failed to load _MALLOC.EXE and the initialisation fails.
Every time the .DLL is called it checks whether the initialisation was
successful. If it was not, the .DLL will return with error.
_MALLOC.EXE also registers the same Windows message. Its message
procedure contains the following code extract.
UINT uRegMessage;
#define MAL_ACK 1
.
.
.
if (msg == uRegMessage)
{return SendMessage ((HWND) wParam, WM_COMMAND, HIWORD (lParam),
MAKELPARAM (hWnd, MAL_ACK));
} // endif
Where uRegMessage is the value returned by the
RegisterWindowsMessage function.
When it receives the registered message, it sends a WM_COMMAND
message to the sender with its hWnd in the loword of lParam. The
following code extract is from the hidden test window of MALLOC.DLL:
LRESULT CALLBACK TestProc (HWND hWndTest, UINT msg, WPARAM wParam,
LPARAM lParam)
{if (msg == WM_COMMAND)
{if (wParam == TEST_ID && HIWORD (lParam) == MAL_ACK)
{hWnd = (HWND) LOWORD (lParam);
} // endif
return 0;
} // endif
return DefWindowProc (hWndTest, msg, wParam, lParam);
- 54 -
} // end TestProc
The test window then saves the hWnd of _MALLOC.EXE s hidden
window to the variable hWnd. As hWnd is initialised to NULL every
time the .DLL is loaded, a NULL value indicates that the test window
did not receive the WM_COMMAND message. This implies that _MALLOC.EXE
has not been loaded.
The second section of MALLOC.DLL contains these four API function
calls.
API function name
Description/function
gmalloc
Allocates a block of shareable global memory.
grealloc
Changes the size of an already allocated block of global memory.
gfree
Frees a block of shareable global memory.
gisptr
Checks if a given pointer points to a block of shareable global
memory.
The gmalloc function first checks if _MALLOC.EXE is loaded by
calling _gethwnd. It fails if _MALLOC.EXE cannot be loaded. It then
sends the MAL_ALLOC message to the hidden window of _MALLOC.EXE. The
return value of the message is a pointer to the newly allocated
shareable global memory. If there was an error in the process, the
return value will be NULL. The following code extract is from
_MALLOC.EXE.
void far *lpPtr;
HGLOBAL handle;
.
.
.
case MAL_ALLOC:
handle = GlobalAlloc (GMEM_DDESHARE | GHND, (DWORD) lParam);
if (handle == NULL) return NULL;
lpPtr = GlobalLock (handle);
return (LRESULT) lpPtr;
This code simply allocates shareable global memory, locks it and
returns a pointer to it.
The grealloc function checks if initialisation has been
successful. It then sends the MAL_REALLOC message to the hidden
window of _MALLOC.EXE. It fills the REALLOCSTRUCT structure and
passes a pointer in the lParam of the message. The process is similar
to gmalloc s. When _MALLOC.EXE receives the MAL_REALLOC message, the
following code extract processes it:
typedef struct tagREALLOCSTRUCT
- 55 -
{DWORD dwSize;
void far *lpPtr;
} REALLOCSTRUCT, far *LPREALLOCSTRUCT;
LPREALLOCSTRUCT lpRa;
.
.
.
case MAL_REALLOC:
lpRa = (LPREALLOCSTRUCT) lParam;
handle = (HANDLE) LOWORD (GlobalHandle (SELECTOROF (lpRa->lpPtr)));
if (handle == NULL) return NULL;
if (GlobalUnlock (handle)) return NULL;
handle = GlobalReAlloc (handle, lpRa->dwSize, GMEM_ZEROINIT);
if (handle == NULL) return NULL;
lpPtr = GlobalLock (handle);
return (LRESULT) lpPtr;
The handle to the allocated memory block is first retrieved via a
call to GlobalHandle. The memory block is then unlocked.
GlobalUnlock returns the number of times a handle has been locked. If
the return value is greater than zero (meaning the memory is still
locked), this message returns NULL. Otherwise, a call to
GlobalRealloc is then issued to reallocate the memory. The block of
memory is then locked and the pointer returned.
The gfree function sends the MAL_FREE to the hidden window of
_MALLOC.EXE. The process is similar to grealloc s except that the
block of memory is freed instead of reallocated.
The following code extract shows how gisptr determines whether a
pointer points to shareable global memory:
BOOL WINAPI gisptr (void far *lpPtr)
{HGLOBAL handle;
handle = (HGLOBAL) LOWORD (GlobalHandle ((UINT) FP_SEG (lpPtr)));
if (handle == NULL) return FALSE;
if (FP_OFF (lpPtr) != NULL) return FALSE;
return TRUE;
} // end gisptr
First, it tries to retrieve the handle to the block of memory.
If that fails the pointer is not valid. It then checks if the offset
of the pointer is 0 (NULL). Windows 3.x GlobalLock function always
return pointers with 0 offset. This is not documented so it may
change. The aim of this function is to provide an alternative to
Windows 3.x IsBadHugeReadPtr and IsBadHugeWritePtr API functions.
My methods:
You may argue that you have a better method of determining
whether an application is loaded (i.e., Using FindWindow or
GetModule). Well, you may be right. I justify my methods because;
1. they are different,
2. they serve to illustrate the fact that there are more ways than
- 56 -
one to achieve something in software, and
3. they are meant to demonstrate alternative (and interesting?)
ways.
_MALLOC.EXE creates a hidden window, which only purpose is to
receive messages. I call this kind of windows Message Targets .
They serve only as a method of communications. Windows does not
provide an object that an application can use to receive messages.
The only way is to use a window that is always hidden. _MALLOC.EXE
has no other window. It is a hidden application, meaning the user
will never see it on the screen. In a way, it is like a .DLL. It
provides services that other application can use. You might ask why
don t I use a .DLL instead. The problem is any shareable global
memory allocated by a .DLL is owned by the application that requested
it. Our aim is to allocate global memory that has a longer lifespan
than the application. To achieve that, the global memory must be
allocated by an application that is always active. As this
contradicts with the notion that the user should have the choice of
terminating applications, _MALLOC.EXE is made to be hidden.
Notes:
[1] - Although prior to WIndows 3.0, there were Windows 286 and
Windows 386, these were relatively unpopular and hence this discussion
excludes them.
Other compilers/languages:
The source code provided will compile with Borland C++ version
3.1. To use the binaries with other C compilers, use the included
wmalloc.h (This is located in the INCLUDE directory of SHAREMEM.ZIP)
header file, the associated binaries (_MALLOC.EXE and MALLOC.DLL
--These can be found in the BIN directory of SHAREMEM.ZIP. ) and the
import library, MALLOC.LIB (This is located in the LIB directory of
SHAREMEM.ZIP). Windows.h must be included before wmalloc.h. Turbo
Pascal users may want to create external referenced import functions
to link up with MALLOC.DLL. Visual Basic and Word for Windows users
can use the declare function/sub statements to link up with
MALLOC.DLL.
Next article we will look at ways to use the shareable global
memory blocks.
The author:
I am currently doing a PhD. degree in Electrical And Electronic
Engineering at the University Of Canterbury. My programming
experience dates back to the days where real programmers code with
Z80 machine code. For the past two years I have taken an interest in
Windows programming.
Please send any comments, questions, criticisms to me via
email: chuah@elec.canterbury.ac.nz
or post mail: Dennis Chuah
c/o The Electronic and Electrical Engineering Department
- 57 -
University of Canterbury
Private Bag
Christchurch
New Zealand.
All mail (including hate mail) is appreciated. I will try to
answer all questions personally, or if the answer has general appeal,
in the next issue. If you are sending me email, please make sure you
provide me with an address that I can reach (most internet and bitnet
nodes are reachable, and so is compuserve).
- 58 -
Customizing FileDialog in Visual C++
By Tony Lee
Overview
I have developed two application with Visual C++. Both of them
are fairly good size applications with > 300K lines of C++ source code
each. I use common dialogs extensively in my projects. In this
article, I will try to show you how to use and customize the
CFileDialog class in the Visual C++ environment. I would also like
to share with you why I think C++ and object oriented development
environment is such a great tool for developing Windows programs.
Since I have limited time to write this article, I want to refer
directly to the WinSDK help for most of the basic information related
to Common Dialog. My background: I have been doing Windows
programming for 4 months now. One of the applications I wrote is a
diagnostic utility for a complicated instrument. The other
application I wrote is a C/C++ cross reference utility that lets you
cross reference C/C++ source code. As you can see, Windows
programming with C++ is not hard at all.
What is Common Dialog?
Common dialog is a set of dialog box functions. These functions
simplify the programming for the common dialogs such as File, Color,
Font and Print Dialogs for Windows. You can find more information
about them in the "Common Dialog Box OverView" section of the Windows
SDK Help. [Editor's note: The common dialog routines are in the
COMMDLG.DLL file - mfw]
Why C++?
One of the best reasons for using C++ in developing Windows
programs is encapsulation. The class structure of the C++ langauge
hides the most implicit details from you. For example, to implement
the FileDialog using C and SDK, you have to write about 40 lines of C
code. (You can count them yourself in the "Filename Dialog Boxes
(3.1)" part of the Windows 3.1 SDK help.) You also need to understand
the "OPENFILENAME" structure and the "GetOpenFileName" functions.
To implement the same open file dialog function in C++, you do
the following:
Void OnOpenFile() {
CFileDialog dlg(TRUE, "txt", NULL, OFN_FILEMUSTEXIST |
OFN_HIDEREADONLY, "Text Files (*.txt) | *.txt All Files (*.*) |*.*
||");
if (dlg.DoModal() != IDOK) return ;
CString sFilename = dlg.GetPathName();
// You can use the filename now.
}
As you can see, the C++ implementation of the same feature is
much more elegant and simpler than the C implementation. CFileDialog
encapsulates all of the structures and function initializations.
- 59 -
Drawback of C++
What is the drawback of using C++ in implementing your Windows
project? In my honest opinion, the best reason for using C++ is also
the biggest drawback. If your project's features fit the class
library very well, you can use the class directly or with little
modifications. However, if you want to differentiate your product
from what's avaiable in the market, you will have to modify the
existing class library by creating inheritance from the base class,
understanding the class interface and implementing the needed virtual
functions of the inherited class. The benefit of the encapsulation
in a class library also hide a lot of details from you. You have to
spend time to peel the layers off the class hierarchy and understand
each layer's functionalities so your new class adds features to the
existing class in an optimized fashion. From my experience of doing
C++ projects, to do a reasonable job in deriving a new class from a
class framework you should do the following:
1. You need to understand the existing class hiearchy. In this
example, you should know that the "CFileDialog" class is derived from
the "CDialog" class. CDialog is derived from the "CWnd" class, and so
on. You can find out the MFC class hierarchy relationship from the
MFC Help's "Hierarchy Button".
2. You need to understand what are the public/protected data
members and methods for every classes in that particular hiearchy.
3. You need to understand the virtual functions in the hiearchy
and how these virtual functions interact between layers of the
classes. For example, you should know that the Windows SDK's
subclass mechanism is inherited in the "virtual LRESULT WindowProc(
UINT message, WPARAM wParam, LPARAM lParam ); " function.
As you can see, encapsulation really complicates the process of
understanding the class library. In my project, in order to extend
the existing class successfully, I have to spend about five times the
amount of time to learn the existing C++ code than C code. After I
understand the existing C++ code, I spend about the same amount of
time writing the new code. The biggest benefit of object oriented
design is that the interface to that particular object is very well
defined by the class methods. Almost all of the modifications I made
were internal to the class. The interface between this object to the
rest of the system changes very little. Thus, after I finish
modifying the class, with only minor change to the interface, I can
easily test the new system. Testing of the new system is much
easier.
Why customize the Common Dialog?
The simplest form Common dialog is great, if it fits your needs.
With just six line of C++ code, your users can get the professionally
designed dialog box for open and save file. Most important benefit
of all, there are fewer chance for errors. However, most of the
time, getting a single file name from the dialog box may not be
exactly what you'd like to do. Also properly designed and customized
dialog box make your program easier to use. It adds value and a
- 60 -
professional look to your final product.
For example, in my Interactive Cross Reference for Windows
(IXFW) program, the FileDialog has two additional controls:
1) Add a listbox to the dialog.
2) Add an "Add File" button to the dialog box.
When an user pushs the "Add File" button, the dialog box will add
the selected files in the file listbox to my custom listbox. This
way, the user can select all the files he wants from different
directories before the file dialog box is closed and the program
continues. The user can save a lot of tedious mouse/keyboard actions
when he tries to gather files from different directories. A very
good value is added to the final product.
How to customize the common dialog in C?
The easiest way learn how to customize the file dialog box with C
is by studying the excellent article "Using and Customizing Common
Dialogs", written by Kraig Brockschmidt of Microsoft System Developers
Relations. You can download this file from CompuServe. Basically, to
customize the file dialog, you need to do the following:
1) You need to edit the dialog template by adding the new
controls to the default template.
2) You have to hook the dialog message processing procedure by
subclassing the dialog control.
3) You need to initalize the proper data structure.
4) You have to write message handler code to support the new
controls in your dialog template.
How to customize the Common Dialog in C++?
You customize the File dialog in a very similar fashion.
However, it's much more desirable to use all the Visual C++ 's tools
in the customization process. Here is how to do that:
1) Use the "App Studio" to customize the template.
I did this by inserting the default file dialog template into
my .rc file. You also include the "dlgs.h" in the include section of
the .rc file, because most of the resource IDs defined for the file
dialog box are located in the "dlgs.h" file.
2) Derive a new class from the CFileDialog class with "Class
Wizard".
The best way to do this is to run the Class Wizard from inside
the "App Studio". Since I can't tell the Class Wizard to derive a new
class from the CFileDialog directly, I have to tell the Class Wizard
to derive the new class for the dialog template from the CDialog
class. After the Class Wizard created the necessary files for the
new class, I edited the parent class from CDialog to CFileDialog.
- 61 -
(You need to change the constructor for this particular
implementation.)
3) Subclass the new dialog box.
I didn't do it here, since the dialog box is subclassed
automatically in the MFC class hiearchy. However, if you want to
use any of the fancy features like customizing the Common Dialog, you
really should understand how MFC subclasses a window and how the
messages are routed to your handlers. Since the subclass mechanism
is in the MFC's class hiearchy already, I can use the Class Wizard to
add handler functions to the dialog controls' messages. The handlers
I added are: a) OnOK() - to handle the OK button from the default
actions to the new action of adding selected files to my listbox. b)
OnClickButton1() - for adding files to the listbox. c) OnClickButton2
- for deleting files from the listbox.
I also added several data members which map to the new control
IDs with Class Wizard. This way, I can use the class methods of the
CButton or CListBox instead of trying to figure out how to send
control messages to those IDs. Isn't C++ wonderful?
One last data member I added to this particular class is the
"CStringArray" for storing the collection of filenames after the OK
button is pushed. You see, when you push the OK button, the
filedialog will destoy the dialog box with all the controls in it.
You have to copy the strings from the listbox control to a safe
location before the listbox control is destroyed.
4) Properly initialize the new class in the constructor.
You have to tell the "OPENFILENAME" structure to use the new
template. This is done in the constructor of this particular class.
5) Add methods to retrieve the collections of filenames from the
dialog class.
Since the CStringArray data member is public, I choose to let
the outside world have access to the filelist directly. Not the
purest form of C++, but....
OK, all of the files needed to compile and run the new FileDialog
Box are in the filedia.zip file. Have fun!
* If you have any comments, questions, suggestions, feel free to send
email to Tony Lee at CompuServe 72064,1235.
** "Interactive Cross Reference for Windows" is a shareware utility
that lets you build a database from existing source code so that you
can quickly browse megabytes of source code. You can download the
program from IBMPRO forum of the CIS.
- 62 -
Installing Windows NT
(As Done by a non-Windows Guru)
By Kurt Simmons
A couple of months ago at a company meeting it was decided that
we should get in on the ground floor of software developing for
Windows NT, and that we would order the preliminary release of the
SDK. When I called Microsoft, I found that the CD was $69.00 and that
the documentation would be more than $300, if you wanted a hard copy.
I figured that if it was on the CD that would be sufficient, even if
it was in PostScript format (though in fact some is in Windows Write
format).
After breathlessly waiting a week, the box arrived; finally we
too would enter the next step in giving a PC a "real" operating
system. I had moved many of our files around to make over 200Meg free
on a drive so that even Windows NT could not complain during
installation. Then I found that we can't run Stacker with Windows NT,
nor could we run the current drivers for our LAN (LBL). NT requires
that you use 32bit drivers. While this will improve performance, it
made life difficult at the current time. OK, back to moving files
around and freeing up a drive that is unStacked. Two hours later, I
am back on my way to installing NT.
NTFS: would I like to convert my drive to that format? Options
are available to convert with or without destroying the existing files
or to simply keep the existing FAT format. The first time through I
figured I would keep the existing format (an option exists to convert
later if we desire). After some trial and error of getting NT
configured to work with the Ethernet cards, I decided to give up and
completely re-install as I have changed so many of the settings (you'd
think after doing this sort of thing for this long I would have
learned to keep notes!) So, here I go again installing it for the
second time. By now I have read about the advantages of using the
NTFS for reasons of security and data integrity; I figured that it
would be a good idea to convert the drive to the format that Windows
NT will be most comfortable with.
Everything went along fine until I found out that "Converting
while leaving your files intact" doesn't mean you can boot off of that
drive anymore or even access it from MS-DOS! Well, since I needed to
be able to run DOS as my primary OS at this point, I realized I must
reformat the drive. Except I couldn't do that from within NT. So I
booted from floppy and found that my D: drive was now listed as my
C:, and C: was nowhere to be found. The installation program for
Windows NT was kind enough to reformat the drive in MS-DOS for me
after my partner got done performing CPR.
The moral of the above story is that taking a day or so to search
the on-disk documentation might have done us some good. In the
DOC\ENDUSER\WRITE (this is the first place I would look for
information on "How to plan your installation") directory were a
series of files on installing Windows NT. I found that I had become a
little too used to the standard OS of the PC's being more than a bit
lax in security, I should be able to destroy my system from the inside
if I want to, right?
The first eight hours of NT seemed to have drained me to the
- 63 -
point of actually considering giving up caffeine as a major food
group, but after that life got easier. I hope to be able to continue
my adventures in NT next month. The articles will be written from the
point of a programmer but not a Windows NT guru, as I think many of us
fit that description.
Kurt A. Simmons
Systems Administrator
Millennium Technologies, Inc.
Suite 100
160 Edgehill Rd.
Glenside, Pa 19038-3004
Voice (215) 886-7366 Fax (215) 886-8602
- 64 -
Software Development '93
by Pete Davis
Mike and I went to the Software Development '93 conference in
Boston. The conference ran the week of the August 23-27. Mike and I
were there from the 24th to the 26th. This main point of the
conference was to let Software Developers strut their stuff. All the
big boys were there, Microsoft, Borland, Miller-Freeman and others.
So, what were people showing off? Well, I think the single
product that impressed Mike and I the most, and this is strictly from
a demo point-of-view, but Symantec's C++ development environment won
hands down for best demo. They also won in the best "Star Trek: The
Next Generation" rip-off. Basically they did a big show as part of
their demo. It was cute, but the demo was really impressive. The
development is a lot like Visual C++, but better. You can create your
dialog boxes, menus, and other resources from within Symantec's
Integrated Development and Debugging Environment (IDDE). You tie the
buttons to dialog boxes and dialog boxes to menu items, and so on and
so on. All the code will be generated by the IDDE. You can even throw
in things like tool-bars. Yep, that's right, just throw in the tool
bar, make your own icons for it and bang, all the code you need is
there. It also allows you to go in and modify the code and have new
generations of the design take on your existing code changes. We're
hoping to get a review copy of this soon. It's a great product and it
deserves some good press. (That's a hint to any of you who happen to
work for Symantec!!!).
Another product that really blew peoples socks off was Nu-Mega's
Bounds Checker 2.0, which was officially released during the
conference. This new version of Bounds Checker has terrific
improvements on version 1. More checking is done and the best feature
is the new event tracing feature. Essentially, it will trace all API
and library calls made by your program and keep track of what
parameters were passed to each. It also keeps track of messages and
other things. Really, it's just a plain fantastic product. Look
forward to a review in the next issue.
Microsoft is working on MFC 2.5. One of the main improvements is
encapsulation of OLE 2.0. A lot of people have agreed that OLE 2.0 is
just too big and bulky for most people. MFC 2.5 is going to fix that
problem. What about us C programmers? Well, the best thing to do might
be to do all the OLE 2.0 work in C++ and then do the rest of your
coding in C. Really, doing OLE 2.0 in C is just about impossible. In
fact, we went to a class taught by a Microsoft employee, Nigel
Thompson. Nigel humorously described his 4.5 months learning to do
simple OLE (as he says, Objectionable Learning Experience) 2.0 things
in C, like containers. We're talking about a guy who has, as a
resource, the people who wrote OLE 2.0. Obviously if it took him 4.5
months to do this, most of us would probably be better off doing it
with MFC 2.5, which is supposed to cut that 4.5 month learning curve
down to 30 minutes or so. A slight improvement!
Borland announced OWL 2.0 which is planned to be a cross-platform
class library. We'll see how far that goes. Microsoft released Visual
C++ for Win32. That was all pretty public, though.
We got to meet some of our readers who recognized the names on
- 65 -
our badges. I have to say, I was awfully surprised to have people
recognize me at the conference. Anyway, the entire conference seemed
to be a big success and Mike and I certainly enjoyed meeting everyone
there, especially the readers that made it.
We were able to get some review copies of software and books, so
you'll be seeing some reviews of some really good products and books
in the near future. Hopefully Mike and I will have enough warning for
the next conference that we can let you all know we'll be there.
Hopefully we'll be able to run into some more of you at other
conferences. It's nice to meet the readers and hear first-hand what
they have to say about the magazine.
I'd like to thank all the people who made SD '93 so much fun for
me. Especially, Michael Liben, Matt Pietrek and all the guys at Nu-
Mega, Dave Thielen, Paul Yao, Julianne Sharer and the others at
WexTech, all the guys at ProtoView Development for being so cool,
Richard Hale Shaw, and Matt Trask. Those are the only ones I can
remember off the top of my head.
- 66 -
Getting In Touch With Us
Internet: 71644.3570@compuserve.com
GEnie: P.DAVIS5 (Pete)
CompuServe: 71141,2071 (Mike) 71644,3570 (Pete)
WPJ BBS (703) 503-3021 (Mike and Pete)
You can also send paper mail to:
Windows Programmer's Journal
9436 Mirror Pond Drive
Fairfax, VA 22032
U.S.A.
In future issues we will be posting e-mail addresses of
contributors and columnists who don't mind you knowing their
addresses. We will also contact any writers from previous issues and
see if they want their mail addresses made available for you to
respond to them. For now, send your comments to us and we'll forward
them.
- 67 -
The Last Page
by Mike Wallace
"The first thing we do, we kill all the OS/2 programmers."
-William Shakespeare
No, this isn't an OS/2-bashing column. Pete and I spent a couple
of days last week at the Software Development '93 convention in Boston
meeting people and going to conferences (see Pete's "Software
Development '93" article elsewhere in this issue). Here are some
notes:
One of the speakers I saw (from Borland) gave a presentation on
an informal survey he took of programmers. One of the questions he
asked was whether they thought programming was an art or a science. A
majority said "an art", and I agreed with this at first, but then
after thinking about it for a while I had to change my mind. Take
physics, for example. Most people would consider that a science
(versus an art). What's involved with a physicist's work? Usually it
starts off with an idea or an observation about the way things are in
nature. He then takes that idea and applies the laws of physics to it
in an effort to understand what's happening. He's "doing Physics."
For instance, in 1960, Edward Lorenz created a machine that simulated
weather (the "Royal McBee") and after staring at it for a while, he
thought he noticed a pattern to the cloud formations. He started
working on the math, and along the way helped create the field of
chaos, an unusual and fascinating branch of physics. My point is that
he had an initial burst of creative thought that preceded the
remainder of his work in the field, which sounds like programming.
When I start on a program, I usually first think about how I'm going
to approach the problem and then I use what I know about programming
to implement my solution, so maybe programming is as much of a science
as Physics, and not so much an art.
Another question this speaker asked in his survey was about
favorite foods for programmers. The winners were pizza, beer and
hamburgers. The loser, coming in as the favorite food of 1% of those
programmers surveyed, was fresh vegetables. I'm glad I'm not the only
one favoring beef over beets.
The speaker also asked about favorite outside hobbies. The
winner was "Watching Star Trek reruns". How did I know he was going
to say that? The audience gave a very loud cheer when he announced
that. Am I the only programmer who doesn't watch "The Next
Generation"? [Pete's Note: Don't buy that for a second. Mike reminded
me it was on the other night! Obviously he keeps better track of it
than me!] I went to Symantec's party where they announced their C++
compiler, and it was a 30-minute skit straight out of "Star Trek".
They talked about getting to the planet C++ and fighting the
"Borland-ians" and "Microsoft-ians" from their space ship. It was
pretty corny.
I spent a good while talking to the president of a well-known
software company about what albums are good for prgramming to (you can
tell I did a lot of work at this convention). We agreed on Pink
Floyd's "The Wall". I'd also have to nominate Pearl Jam's "Ten" and
Joe Satriani's "Surfing with the Alien". I want your responses. What
- 68 -
music do you listen to when you're pounding out code? Van Halen with
Visual Basic? "Purple Haze" with Pascal? Send me your comments, and
I'll reprint the best ones after I get enough for a column (so it may
take a while). Hope you enjoy this issue. Talk to you next month.
- 69 -